Merge pull request #3051 from iced-rs/replace-dark-light
System Theme Reactions
This commit is contained in:
commit
a9091f9edd
45 changed files with 1069 additions and 594 deletions
734
Cargo.lock
generated
734
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
18
Cargo.toml
18
Cargo.toml
|
|
@ -22,7 +22,7 @@ all-features = true
|
|||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[features]
|
||||
default = ["wgpu", "tiny-skia", "crisp", "web-colors", "auto-detect-theme", "thread-pool"]
|
||||
default = ["wgpu", "tiny-skia", "crisp", "web-colors", "thread-pool", "linux-theme-detection"]
|
||||
# Enables the `wgpu` GPU-accelerated renderer backend
|
||||
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
|
||||
# Enables the `tiny-skia` software renderer backend
|
||||
|
|
@ -54,21 +54,19 @@ tokio = ["iced_futures/tokio"]
|
|||
# Enables `smol` as the `executor::Default` on native platforms
|
||||
smol = ["iced_futures/smol"]
|
||||
# Enables querying system information
|
||||
system = ["iced_winit/system"]
|
||||
sysinfo = ["iced_winit/sysinfo"]
|
||||
# Enables broken "sRGB linear" blending to reproduce color management of the Web
|
||||
web-colors = ["iced_renderer/web-colors"]
|
||||
# Enables pixel snapping for crisp edges by default (can cause jitter!)
|
||||
crisp = ["iced_core/crisp", "iced_widget/crisp"]
|
||||
# Enables the WebGL backend
|
||||
webgl = ["iced_renderer/webgl"]
|
||||
# Enables syntax highligthing
|
||||
# Enables syntax highlighting
|
||||
highlighter = ["iced_highlighter", "iced_widget/highlighter"]
|
||||
# Enables the advanced module
|
||||
advanced = ["iced_core/advanced", "iced_widget/advanced"]
|
||||
# Embeds Fira Sans into the final application; useful for testing and Wasm builds
|
||||
fira-sans = ["iced_renderer/fira-sans"]
|
||||
# Auto-detects light/dark mode for the built-in theme
|
||||
auto-detect-theme = ["iced_core/auto-detect-theme"]
|
||||
# Enables basic text shaping by default
|
||||
basic-shaping = ["iced_core/basic-shaping"]
|
||||
# Enables advanced text shaping by default
|
||||
|
|
@ -79,6 +77,8 @@ strict-assertions = ["iced_renderer/strict-assertions"]
|
|||
unconditional-rendering = ["iced_winit/unconditional-rendering"]
|
||||
# Enables support for the `sipper` library
|
||||
sipper = ["iced_runtime/sipper"]
|
||||
# Enables Linux system theme detection
|
||||
linux-theme-detection = ["iced_winit/linux-theme-detection"]
|
||||
|
||||
[dependencies]
|
||||
iced_debug.workspace = true
|
||||
|
|
@ -175,10 +175,9 @@ bytemuck = { version = "1.0", features = ["derive"] }
|
|||
bytes = "1.6"
|
||||
cargo-hot = { package = "cargo-hot-protocol", git = "https://github.com/hecrj/cargo-hot.git", rev = "b8dc518b8640928178a501257e353b73bc06cf47" }
|
||||
cosmic-text = "0.14"
|
||||
dark-light = "2.0"
|
||||
cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "453cedec0d2ec563bd7fa87e84a2319bcebb1ba3" }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
glam = "0.25"
|
||||
cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "453cedec0d2ec563bd7fa87e84a2319bcebb1ba3" }
|
||||
guillotiere = "0.6"
|
||||
half = "2.2"
|
||||
image = { version = "0.25", default-features = false }
|
||||
|
|
@ -188,16 +187,17 @@ lilt = "0.8"
|
|||
log = "0.4"
|
||||
lyon = "1.0"
|
||||
lyon_path = "1.0"
|
||||
mundy = { version = "0.2", default-features = false }
|
||||
num-traits = "0.2"
|
||||
ouroboros = "0.18"
|
||||
png = "0.17"
|
||||
png = "0.18"
|
||||
pulldown-cmark = "0.12"
|
||||
qrcode = { version = "0.13", default-features = false }
|
||||
raw-window-handle = "0.6"
|
||||
resvg = "0.42"
|
||||
rustc-hash = "2.0"
|
||||
serde = "1.0"
|
||||
semver = "1.0"
|
||||
serde = "1.0"
|
||||
sha2 = "0.10"
|
||||
sipper = "0.1"
|
||||
smol = "2"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ keywords.workspace = true
|
|||
workspace = true
|
||||
|
||||
[features]
|
||||
auto-detect-theme = ["dep:dark-light"]
|
||||
advanced = []
|
||||
crisp = []
|
||||
basic-shaping = []
|
||||
|
|
@ -32,9 +31,6 @@ smol_str.workspace = true
|
|||
thiserror.workspace = true
|
||||
web-time.workspace = true
|
||||
|
||||
dark-light.workspace = true
|
||||
dark-light.optional = true
|
||||
|
||||
serde.workspace = true
|
||||
serde.optional = true
|
||||
serde.features = ["derive"]
|
||||
|
|
|
|||
|
|
@ -166,31 +166,6 @@ impl Theme {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
#[cfg(feature = "auto-detect-theme")]
|
||||
{
|
||||
use std::sync::LazyLock;
|
||||
|
||||
static DEFAULT: LazyLock<Theme> = LazyLock::new(|| {
|
||||
match dark_light::detect()
|
||||
.unwrap_or(dark_light::Mode::Unspecified)
|
||||
{
|
||||
dark_light::Mode::Dark => Theme::Dark,
|
||||
dark_light::Mode::Light | dark_light::Mode::Unspecified => {
|
||||
Theme::Light
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
DEFAULT.clone()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "auto-detect-theme"))]
|
||||
Theme::Light
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Theme {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
|
|
@ -256,6 +231,18 @@ impl fmt::Display for Custom {
|
|||
}
|
||||
}
|
||||
|
||||
/// A theme mode, denoting the tone or brightness of a theme.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum Mode {
|
||||
/// No specific tone.
|
||||
#[default]
|
||||
None,
|
||||
/// A mode referring to themes with light tones.
|
||||
Light,
|
||||
/// A mode referring to themes with dark tones.
|
||||
Dark,
|
||||
}
|
||||
|
||||
/// The base style of a theme.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Style {
|
||||
|
|
@ -268,7 +255,13 @@ pub struct Style {
|
|||
|
||||
/// The default blank style of a theme.
|
||||
pub trait Base {
|
||||
/// Returns the default base [`Style`] of a theme.
|
||||
/// Returns the default theme for the preferred [`Mode`].
|
||||
fn default(preference: Mode) -> Self;
|
||||
|
||||
/// Returns the [`Mode`] of the theme.
|
||||
fn mode(&self) -> Mode;
|
||||
|
||||
/// Returns the default base [`Style`] of the theme.
|
||||
fn base(&self) -> Style;
|
||||
|
||||
/// Returns the color [`Palette`] of the theme.
|
||||
|
|
@ -280,6 +273,39 @@ pub trait Base {
|
|||
}
|
||||
|
||||
impl Base for Theme {
|
||||
fn default(preference: Mode) -> Self {
|
||||
use std::env;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static SYSTEM: OnceLock<Option<Theme>> = OnceLock::new();
|
||||
|
||||
let system = SYSTEM.get_or_init(|| {
|
||||
let name = env::var("ICED_THEME").ok()?;
|
||||
|
||||
Theme::ALL
|
||||
.iter()
|
||||
.find(|theme| theme.to_string() == name)
|
||||
.cloned()
|
||||
});
|
||||
|
||||
if let Some(system) = system {
|
||||
return system.clone();
|
||||
}
|
||||
|
||||
match preference {
|
||||
Mode::None | Mode::Light => Self::Light,
|
||||
Mode::Dark => Self::Dark,
|
||||
}
|
||||
}
|
||||
|
||||
fn mode(&self) -> Mode {
|
||||
if self.extended_palette().is_dark {
|
||||
Mode::Dark
|
||||
} else {
|
||||
Mode::Light
|
||||
}
|
||||
}
|
||||
|
||||
fn base(&self) -> Style {
|
||||
default(self)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ mod time_machine;
|
|||
|
||||
use crate::core::border;
|
||||
use crate::core::keyboard;
|
||||
use crate::core::theme::{self, Base, Theme};
|
||||
use crate::core::theme::{self, Theme};
|
||||
use crate::core::time::seconds;
|
||||
use crate::core::window;
|
||||
use crate::core::{Alignment::Center, Color, Element, Length::Fill};
|
||||
|
|
@ -90,7 +90,11 @@ where
|
|||
state.subscription(&self.program)
|
||||
}
|
||||
|
||||
fn theme(&self, state: &Self::State, window: window::Id) -> Self::Theme {
|
||||
fn theme(
|
||||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Option<Self::Theme> {
|
||||
state.theme(&self.program, window)
|
||||
}
|
||||
|
||||
|
|
@ -307,14 +311,12 @@ where
|
|||
}
|
||||
};
|
||||
|
||||
let theme = program.theme(state, window);
|
||||
|
||||
let derive_theme = move || {
|
||||
fn derive_theme<T: theme::Base>(theme: &T) -> Theme {
|
||||
theme
|
||||
.palette()
|
||||
.map(|palette| Theme::custom("iced devtools", palette))
|
||||
.unwrap_or_default()
|
||||
};
|
||||
.unwrap_or(Theme::Dark)
|
||||
}
|
||||
|
||||
let mode = match &self.mode {
|
||||
Mode::None => None,
|
||||
|
|
@ -340,7 +342,7 @@ where
|
|||
}
|
||||
}
|
||||
.map(|mode| {
|
||||
themer(derive_theme(), Element::from(mode).map(Event::Message))
|
||||
themer(derive_theme, Element::from(mode).map(Event::Message))
|
||||
});
|
||||
|
||||
let notification = self
|
||||
|
|
@ -359,7 +361,7 @@ where
|
|||
.push_maybe(mode.map(opaque))
|
||||
.push_maybe(notification.map(|notification| {
|
||||
themer(
|
||||
derive_theme(),
|
||||
derive_theme,
|
||||
bottom_right(opaque(
|
||||
container(notification)
|
||||
.padding(10)
|
||||
|
|
@ -389,7 +391,7 @@ where
|
|||
Subscription::batch([subscription, hotkeys, commands])
|
||||
}
|
||||
|
||||
fn theme(&self, program: &P, window: window::Id) -> P::Theme {
|
||||
fn theme(&self, program: &P, window: window::Id) -> Option<P::Theme> {
|
||||
program.theme(self.state(), window)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use iced::{Element, Fill, Point, Rectangle, Renderer, Subscription, Theme};
|
|||
pub fn main() -> iced::Result {
|
||||
iced::application(Arc::new, Arc::update, Arc::view)
|
||||
.subscription(Arc::subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.theme(Theme::Dark)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use iced::{Element, Theme};
|
|||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(Example::default, Example::update, Example::view)
|
||||
.theme(|_| Theme::CatppuccinMocha)
|
||||
.theme(Theme::CatppuccinMocha)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ impl Events {
|
|||
}
|
||||
Message::EventOccurred(event) => {
|
||||
if let Event::Window(window::Event::CloseRequested) = event {
|
||||
window::get_latest().and_then(window::close)
|
||||
window::latest().and_then(window::close)
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ impl Events {
|
|||
|
||||
Task::none()
|
||||
}
|
||||
Message::Exit => window::get_latest().and_then(window::close),
|
||||
Message::Exit => window::latest().and_then(window::close),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ enum Message {
|
|||
impl Exit {
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::Confirm => window::get_latest().and_then(window::close),
|
||||
Message::Confirm => window::latest().and_then(window::close),
|
||||
Message::Exit => {
|
||||
self.show_confirm = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use iced::{
|
|||
pub fn main() -> iced::Result {
|
||||
iced::application(Image::default, Image::update, Image::view)
|
||||
.subscription(Image::subscription)
|
||||
.theme(|_| Theme::TokyoNight)
|
||||
.theme(Theme::TokyoNight)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ pub fn main() -> iced::Result {
|
|||
|
||||
iced::application(GameOfLife::default, GameOfLife::update, GameOfLife::view)
|
||||
.subscription(GameOfLife::subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.theme(Theme::Dark)
|
||||
.centered()
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ pub fn main() -> iced::Result {
|
|||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
struct Layout {
|
||||
example: Example,
|
||||
explain: bool,
|
||||
theme: Theme,
|
||||
theme: Option<Theme>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -51,7 +51,7 @@ impl Layout {
|
|||
self.explain = explain;
|
||||
}
|
||||
Message::ThemeSelected(theme) => {
|
||||
self.theme = theme;
|
||||
self.theme = Some(theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +74,8 @@ impl Layout {
|
|||
horizontal_space(),
|
||||
checkbox("Explain", self.explain)
|
||||
.on_toggle(Message::ExplainToggled),
|
||||
pick_list(Theme::ALL, Some(&self.theme), Message::ThemeSelected),
|
||||
pick_list(Theme::ALL, self.theme.as_ref(), Message::ThemeSelected)
|
||||
.placeholder("Theme"),
|
||||
]
|
||||
.spacing(20)
|
||||
.align_y(Center);
|
||||
|
|
@ -116,7 +117,7 @@ impl Layout {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
fn theme(&self) -> Option<Theme> {
|
||||
self.theme.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ impl Example {
|
|||
return Task::none();
|
||||
};
|
||||
|
||||
window::get_position(*last_window)
|
||||
window::position(*last_window)
|
||||
.then(|last_position| {
|
||||
let position = last_position.map_or(
|
||||
window::Position::Default,
|
||||
|
|
@ -138,12 +138,8 @@ impl Example {
|
|||
}
|
||||
}
|
||||
|
||||
fn theme(&self, window: window::Id) -> Theme {
|
||||
if let Some(window) = self.windows.get(&window) {
|
||||
window.theme.clone()
|
||||
} else {
|
||||
Theme::default()
|
||||
}
|
||||
fn theme(&self, window: window::Id) -> Option<Theme> {
|
||||
Some(self.windows.get(&window)?.theme.clone())
|
||||
}
|
||||
|
||||
fn scale_factor(&self, window: window::Id) -> f32 {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ struct QRGenerator {
|
|||
data: String,
|
||||
qr_code: Option<qr_code::Data>,
|
||||
total_size: Option<f32>,
|
||||
theme: Theme,
|
||||
theme: Option<Theme>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -58,7 +58,7 @@ impl QRGenerator {
|
|||
self.total_size = Some(total_size);
|
||||
}
|
||||
Message::ThemeChanged(theme) => {
|
||||
self.theme = theme;
|
||||
self.theme = Some(theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,7 +78,8 @@ impl QRGenerator {
|
|||
|
||||
let choose_theme = row![
|
||||
text("Theme:"),
|
||||
pick_list(Theme::ALL, Some(&self.theme), Message::ThemeChanged,)
|
||||
pick_list(Theme::ALL, self.theme.as_ref(), Message::ThemeChanged)
|
||||
.placeholder("Theme")
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(Center);
|
||||
|
|
@ -107,7 +108,7 @@ impl QRGenerator {
|
|||
center(content).padding(20).into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
fn theme(&self) -> Option<Theme> {
|
||||
self.theme.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ impl Example {
|
|||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::Screenshot => {
|
||||
return window::get_latest()
|
||||
return window::latest()
|
||||
.and_then(window::screenshot)
|
||||
.map(Message::Screenshotted);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ pub fn main() -> iced::Result {
|
|||
|
||||
#[derive(Default)]
|
||||
struct Styling {
|
||||
theme: Theme,
|
||||
theme: Option<Theme>,
|
||||
input_value: String,
|
||||
slider_value: f32,
|
||||
checkbox_value: bool,
|
||||
|
|
@ -32,13 +32,14 @@ enum Message {
|
|||
TogglerToggled(bool),
|
||||
PreviousTheme,
|
||||
NextTheme,
|
||||
ClearTheme,
|
||||
}
|
||||
|
||||
impl Styling {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::ThemeChanged(theme) => {
|
||||
self.theme = theme;
|
||||
self.theme = Some(theme);
|
||||
}
|
||||
Message::InputChanged(value) => self.input_value = value,
|
||||
Message::ButtonPressed => {}
|
||||
|
|
@ -46,21 +47,29 @@ impl Styling {
|
|||
Message::CheckboxToggled(value) => self.checkbox_value = value,
|
||||
Message::TogglerToggled(value) => self.toggler_value = value,
|
||||
Message::PreviousTheme | Message::NextTheme => {
|
||||
if let Some(current) = Theme::ALL
|
||||
.iter()
|
||||
.position(|candidate| &self.theme == candidate)
|
||||
{
|
||||
self.theme = if matches!(message, Message::NextTheme) {
|
||||
Theme::ALL[(current + 1) % Theme::ALL.len()].clone()
|
||||
} else if current == 0 {
|
||||
let current = Theme::ALL.iter().position(|candidate| {
|
||||
self.theme.as_ref() == Some(candidate)
|
||||
});
|
||||
|
||||
self.theme = Some(if matches!(message, Message::NextTheme) {
|
||||
Theme::ALL[current.map(|current| current + 1).unwrap_or(0)
|
||||
% Theme::ALL.len()]
|
||||
.clone()
|
||||
} else {
|
||||
let current = current.unwrap_or(0);
|
||||
|
||||
if current == 0 {
|
||||
Theme::ALL
|
||||
.last()
|
||||
.expect("Theme::ALL must not be empty")
|
||||
.clone()
|
||||
} else {
|
||||
Theme::ALL[current - 1].clone()
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Message::ClearTheme => {
|
||||
self.theme = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,8 +77,9 @@ impl Styling {
|
|||
fn view(&self) -> Element<'_, Message> {
|
||||
let choose_theme = column![
|
||||
text("Theme:"),
|
||||
pick_list(Theme::ALL, Some(&self.theme), Message::ThemeChanged)
|
||||
.width(Fill),
|
||||
pick_list(Theme::ALL, self.theme.as_ref(), Message::ThemeChanged)
|
||||
.width(Fill)
|
||||
.placeholder("System"),
|
||||
]
|
||||
.spacing(10);
|
||||
|
||||
|
|
@ -186,11 +196,14 @@ impl Styling {
|
|||
keyboard::key::Named::ArrowDown
|
||||
| keyboard::key::Named::ArrowRight,
|
||||
) => Some(Message::NextTheme),
|
||||
keyboard::Key::Named(keyboard::key::Named::Space) => {
|
||||
Some(Message::ClearTheme)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
fn theme(&self) -> Option<Theme> {
|
||||
self.theme.clone()
|
||||
}
|
||||
}
|
||||
|
|
@ -210,9 +223,7 @@ mod tests {
|
|||
.cloned()
|
||||
.map(|theme| {
|
||||
let mut styling = Styling::default();
|
||||
styling.update(Message::ThemeChanged(theme));
|
||||
|
||||
let theme = styling.theme();
|
||||
styling.update(Message::ThemeChanged(theme.clone()));
|
||||
|
||||
let mut ui = simulator(styling.view());
|
||||
let snapshot = ui.snapshot(&theme)?;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced.workspace = true
|
||||
iced.features = ["system"]
|
||||
iced.features = ["sysinfo"]
|
||||
|
||||
bytesize = "1.1"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use iced::system;
|
||||
use iced::widget::{button, center, column, text};
|
||||
use iced::{Element, Task, system};
|
||||
use iced::{Element, Task};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(Example::new, Example::update, Example::view).run()
|
||||
|
|
@ -26,7 +27,7 @@ impl Example {
|
|||
fn new() -> (Self, Task<Message>) {
|
||||
(
|
||||
Self::Loading,
|
||||
system::fetch_information().map(Message::InformationReceived),
|
||||
system::information().map(Message::InformationReceived),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use iced::{Center, Element, Fill, Font, Right, Theme};
|
|||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(Table::new, Table::update, Table::view)
|
||||
.theme(|_| Theme::CatppuccinMocha)
|
||||
.theme(Theme::CatppuccinMocha)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
99f418007af163f172e163565f166da31015521e1bf7de95fa55cda2fb5a7db5
|
||||
0acb67235c6a11014a2d2b825e0a70069bca0c67bee0cdb38a0144fc72b25220
|
||||
|
|
@ -5,7 +5,7 @@ use iced::widget::{
|
|||
};
|
||||
use iced::window;
|
||||
use iced::{
|
||||
Center, Element, Fill, Font, Function, Subscription, Task as Command,
|
||||
Center, Element, Fill, Font, Function, Subscription, Task as Command, Theme,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -151,7 +151,7 @@ impl Todos {
|
|||
widget::focus_next()
|
||||
}
|
||||
}
|
||||
Message::ToggleFullscreen(mode) => window::get_latest()
|
||||
Message::ToggleFullscreen(mode) => window::latest()
|
||||
.and_then(move |window| window::set_mode(window, mode)),
|
||||
Message::Loaded(_) => Command::none(),
|
||||
};
|
||||
|
|
@ -194,7 +194,7 @@ impl Todos {
|
|||
let title = text("todos")
|
||||
.width(Fill)
|
||||
.size(100)
|
||||
.color([0.5, 0.5, 0.5])
|
||||
.style(subtle)
|
||||
.align_x(Center);
|
||||
|
||||
let input = text_input("What needs to be done?", input_value)
|
||||
|
|
@ -447,7 +447,7 @@ fn empty_message(message: &str) -> Element<'_, Message> {
|
|||
.width(Fill)
|
||||
.size(25)
|
||||
.align_x(Center)
|
||||
.color([0.7, 0.7, 0.7]),
|
||||
.style(subtle),
|
||||
)
|
||||
.height(200)
|
||||
.into()
|
||||
|
|
@ -471,6 +471,12 @@ fn delete_icon() -> Text<'static> {
|
|||
icon('\u{F1F8}')
|
||||
}
|
||||
|
||||
fn subtle(theme: &Theme) -> text::Style {
|
||||
text::Style {
|
||||
color: Some(theme.extended_palette().background.strongest.color),
|
||||
}
|
||||
}
|
||||
|
||||
// Persistence
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct SavedState {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
use iced::border;
|
||||
use iced::widget::{Button, Column, Container, Slider};
|
||||
use iced::widget::{
|
||||
button, center_x, center_y, checkbox, column, horizontal_space, image,
|
||||
radio, rich_text, row, scrollable, slider, span, text, text_input, toggler,
|
||||
vertical_space,
|
||||
};
|
||||
use iced::{Center, Color, Element, Fill, Font, Pixels, Theme};
|
||||
use iced::{Center, Color, Element, Fill, Font, Pixels, color};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
|
@ -201,7 +200,7 @@ impl Tour {
|
|||
Self::container("Welcome!")
|
||||
.push(
|
||||
"This is a simple tour meant to showcase a bunch of \
|
||||
widgets that can be easily implemented on top of Iced.",
|
||||
widgets that come bundled in Iced.",
|
||||
)
|
||||
.push(
|
||||
"Iced is a cross-platform GUI library for Rust focused on \
|
||||
|
|
@ -216,28 +215,19 @@ impl Tour {
|
|||
built on top of wgpu, a graphics library supporting Vulkan, \
|
||||
Metal, DX11, and DX12.",
|
||||
)
|
||||
.push({
|
||||
let theme = Theme::default();
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
.push(
|
||||
rich_text![
|
||||
"Additionally, this tour can also run on WebAssembly ",
|
||||
"by leveraging ",
|
||||
span("trunk")
|
||||
.color(palette.primary.base.color)
|
||||
.background(palette.background.weakest.color)
|
||||
.border(
|
||||
border::rounded(2)
|
||||
.width(1)
|
||||
.color(palette.background.weak.color)
|
||||
)
|
||||
.padding([0, 2])
|
||||
.color(color!(0x7777FF))
|
||||
.underline(true)
|
||||
.font(Font::MONOSPACE)
|
||||
.link(Message::OpenTrunk),
|
||||
"."
|
||||
]
|
||||
.on_link_click(std::convert::identity)
|
||||
})
|
||||
.on_link_click(std::convert::identity),
|
||||
)
|
||||
.push(
|
||||
"You will need to interact with the UI in order to reach \
|
||||
the end!",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub fn main() -> iced::Result {
|
|||
VectorialText::update,
|
||||
VectorialText::view,
|
||||
)
|
||||
.theme(|_| Theme::Dark)
|
||||
.theme(Theme::Dark)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use iced::{
|
|||
pub fn main() -> iced::Result {
|
||||
iced::application(Example::default, Example::update, Example::view)
|
||||
.subscription(Example::subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.theme(Theme::Dark)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ where
|
|||
event: Event::Window(window::Event::RedrawRequested(_)),
|
||||
..
|
||||
}
|
||||
| subscription::Event::SystemThemeChanged(_)
|
||||
| subscription::Event::PlatformSpecific(_) => None,
|
||||
subscription::Event::Interaction {
|
||||
window,
|
||||
|
|
@ -66,7 +67,8 @@ where
|
|||
event,
|
||||
status,
|
||||
} => f(event, status, window),
|
||||
subscription::Event::PlatformSpecific(_) => None,
|
||||
subscription::Event::SystemThemeChanged(_)
|
||||
| subscription::Event::PlatformSpecific(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ mod tracker;
|
|||
pub use tracker::Tracker;
|
||||
|
||||
use crate::core::event;
|
||||
use crate::core::theme;
|
||||
use crate::core::window;
|
||||
use crate::futures::Stream;
|
||||
use crate::{BoxStream, MaybeSend};
|
||||
|
|
@ -27,6 +28,9 @@ pub enum Event {
|
|||
status: event::Status,
|
||||
},
|
||||
|
||||
/// The system theme has changed.
|
||||
SystemThemeChanged(theme::Mode),
|
||||
|
||||
/// A platform specific event.
|
||||
PlatformSpecific(PlatformSpecific),
|
||||
}
|
||||
|
|
@ -422,7 +426,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn filter_map<I, F, T>(id: I, f: F) -> Subscription<T>
|
||||
/// Creatges a [`Subscription`] from a hashable id and a filter function.
|
||||
pub fn filter_map<I, F, T>(id: I, f: F) -> Subscription<T>
|
||||
where
|
||||
I: Hash + 'static,
|
||||
F: Fn(Event) -> Option<T> + MaybeSend + 'static,
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ pub trait Compositor: Sized {
|
|||
);
|
||||
|
||||
/// Returns [`Information`] used by this [`Compositor`].
|
||||
fn fetch_information(&self) -> Information;
|
||||
fn information(&self) -> Information;
|
||||
|
||||
/// Loads a font from its bytes.
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
||||
|
|
@ -178,7 +178,7 @@ impl Compositor for () {
|
|||
|
||||
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
||||
|
||||
fn fetch_information(&self) -> Information {
|
||||
fn information(&self) -> Information {
|
||||
Information {
|
||||
adapter: String::from("Null Renderer"),
|
||||
backend: String::from("Null"),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ pub trait Program: Sized {
|
|||
type Message: Message + 'static;
|
||||
|
||||
/// The theme of the program.
|
||||
type Theme: Default + theme::Base;
|
||||
type Theme: theme::Base;
|
||||
|
||||
/// The renderer of the program.
|
||||
type Renderer: Renderer;
|
||||
|
|
@ -86,8 +86,12 @@ pub trait Program: Sized {
|
|||
Subscription::none()
|
||||
}
|
||||
|
||||
fn theme(&self, _state: &Self::State, _window: window::Id) -> Self::Theme {
|
||||
<Self::Theme as Default>::default()
|
||||
fn theme(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
_window: window::Id,
|
||||
) -> Option<Self::Theme> {
|
||||
None
|
||||
}
|
||||
|
||||
fn style(&self, _state: &Self::State, theme: &Self::Theme) -> theme::Style {
|
||||
|
|
@ -152,7 +156,7 @@ pub fn with_title<P: Program>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
self.program.theme(state, window)
|
||||
}
|
||||
|
||||
|
|
@ -238,7 +242,7 @@ pub fn with_subscription<P: Program>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
self.program.theme(state, window)
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +268,7 @@ pub fn with_subscription<P: Program>(
|
|||
/// Decorates a [`Program`] with the given theme function.
|
||||
pub fn with_theme<P: Program>(
|
||||
program: P,
|
||||
f: impl Fn(&P::State, window::Id) -> P::Theme,
|
||||
f: impl Fn(&P::State, window::Id) -> Option<P::Theme>,
|
||||
) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> {
|
||||
struct WithTheme<P, F> {
|
||||
program: P,
|
||||
|
|
@ -273,7 +277,7 @@ pub fn with_theme<P: Program>(
|
|||
|
||||
impl<P: Program, F> Program for WithTheme<P, F>
|
||||
where
|
||||
F: Fn(&P::State, window::Id) -> P::Theme,
|
||||
F: Fn(&P::State, window::Id) -> Option<P::Theme>,
|
||||
{
|
||||
type State = P::State;
|
||||
type Message = P::Message;
|
||||
|
|
@ -285,7 +289,7 @@ pub fn with_theme<P: Program>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
(self.theme)(state, window)
|
||||
}
|
||||
|
||||
|
|
@ -407,7 +411,7 @@ pub fn with_style<P: Program>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
self.program.theme(state, window)
|
||||
}
|
||||
|
||||
|
|
@ -478,7 +482,7 @@ pub fn with_scale_factor<P: Program>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
self.program.theme(state, window)
|
||||
}
|
||||
|
||||
|
|
@ -561,7 +565,7 @@ pub fn with_executor<P: Program, E: Executor>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
self.program.theme(state, window)
|
||||
}
|
||||
|
||||
|
|
@ -628,7 +632,7 @@ impl<P: Program> Instance<P> {
|
|||
}
|
||||
|
||||
/// Returns the current theme of the [`Instance`].
|
||||
pub fn theme(&self, window: window::Id) -> P::Theme {
|
||||
pub fn theme(&self, window: window::Id) -> Option<P::Theme> {
|
||||
self.program.theme(&self.state, window)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -313,8 +313,8 @@ where
|
|||
delegate!(self, compositor, compositor.load_font(font));
|
||||
}
|
||||
|
||||
fn fetch_information(&self) -> compositor::Information {
|
||||
delegate!(self, compositor, compositor.fetch_information())
|
||||
fn information(&self) -> compositor::Information {
|
||||
delegate!(self, compositor, compositor.information())
|
||||
}
|
||||
|
||||
fn present(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
//! Access the native system.
|
||||
use crate::core::theme;
|
||||
use crate::futures::futures::channel::oneshot;
|
||||
use crate::futures::subscription::{self, Subscription};
|
||||
use crate::task::{self, Task};
|
||||
|
||||
/// An operation to be performed on the system.
|
||||
#[derive(Debug)]
|
||||
pub enum Action {
|
||||
/// Query system information and produce `T` with the result.
|
||||
QueryInformation(oneshot::Sender<Information>),
|
||||
/// Send available system information.
|
||||
GetInformation(oneshot::Sender<Information>),
|
||||
|
||||
/// Send the current system theme mode.
|
||||
GetTheme(oneshot::Sender<theme::Mode>),
|
||||
|
||||
/// Notify to the runtime that the system theme has changed.
|
||||
NotifyTheme(theme::Mode),
|
||||
}
|
||||
|
||||
/// Contains information about the system (e.g. system name, processor, memory, graphics adapter).
|
||||
|
|
@ -37,3 +46,29 @@ pub struct Information {
|
|||
/// Model information for the active graphics adapter
|
||||
pub graphics_adapter: String,
|
||||
}
|
||||
|
||||
/// Returns available system information.
|
||||
pub fn information() -> Task<Information> {
|
||||
task::oneshot(|channel| {
|
||||
crate::Action::System(Action::GetInformation(channel))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the current system theme.
|
||||
pub fn theme() -> Task<theme::Mode> {
|
||||
task::oneshot(|sender| crate::Action::System(Action::GetTheme(sender)))
|
||||
}
|
||||
|
||||
/// Subscribes to system theme changes.
|
||||
pub fn theme_changes() -> Subscription<theme::Mode> {
|
||||
#[derive(Hash)]
|
||||
struct ThemeChanges;
|
||||
|
||||
subscription::filter_map(ThemeChanges, |event| {
|
||||
let subscription::Event::SystemThemeChanged(mode) = event else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(mode)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,12 +266,12 @@ pub fn close<T>(id: Id) -> Task<T> {
|
|||
}
|
||||
|
||||
/// Gets the window [`Id`] of the oldest window.
|
||||
pub fn get_oldest() -> Task<Option<Id>> {
|
||||
pub fn oldest() -> Task<Option<Id>> {
|
||||
task::oneshot(|channel| crate::Action::Window(Action::GetOldest(channel)))
|
||||
}
|
||||
|
||||
/// Gets the window [`Id`] of the latest window.
|
||||
pub fn get_latest() -> Task<Option<Id>> {
|
||||
pub fn latest() -> Task<Option<Id>> {
|
||||
task::oneshot(|channel| crate::Action::Window(Action::GetLatest(channel)))
|
||||
}
|
||||
|
||||
|
|
@ -315,14 +315,14 @@ pub fn set_resize_increments<T>(id: Id, increments: Option<Size>) -> Task<T> {
|
|||
}
|
||||
|
||||
/// Get the window's size in logical dimensions.
|
||||
pub fn get_size(id: Id) -> Task<Size> {
|
||||
pub fn size(id: Id) -> Task<Size> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetSize(id, channel))
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the maximized state of the window with the given [`Id`].
|
||||
pub fn get_maximized(id: Id) -> Task<bool> {
|
||||
pub fn is_maximized(id: Id) -> Task<bool> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetMaximized(id, channel))
|
||||
})
|
||||
|
|
@ -334,7 +334,7 @@ pub fn maximize<T>(id: Id, maximized: bool) -> Task<T> {
|
|||
}
|
||||
|
||||
/// Gets the minimized state of the window with the given [`Id`].
|
||||
pub fn get_minimized(id: Id) -> Task<Option<bool>> {
|
||||
pub fn is_minimized(id: Id) -> Task<Option<bool>> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetMinimized(id, channel))
|
||||
})
|
||||
|
|
@ -346,14 +346,14 @@ pub fn minimize<T>(id: Id, minimized: bool) -> Task<T> {
|
|||
}
|
||||
|
||||
/// Gets the position in logical coordinates of the window with the given [`Id`].
|
||||
pub fn get_position(id: Id) -> Task<Option<Point>> {
|
||||
pub fn position(id: Id) -> Task<Option<Point>> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetPosition(id, channel))
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the scale factor of the window with the given [`Id`].
|
||||
pub fn get_scale_factor(id: Id) -> Task<f32> {
|
||||
pub fn scale_factor(id: Id) -> Task<f32> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetScaleFactor(id, channel))
|
||||
})
|
||||
|
|
@ -365,7 +365,7 @@ pub fn move_to<T>(id: Id, position: Point) -> Task<T> {
|
|||
}
|
||||
|
||||
/// Gets the current [`Mode`] of the window.
|
||||
pub fn get_mode(id: Id) -> Task<Mode> {
|
||||
pub fn mode(id: Id) -> Task<Mode> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetMode(id, channel))
|
||||
})
|
||||
|
|
@ -426,7 +426,7 @@ pub fn show_system_menu<T>(id: Id) -> Task<T> {
|
|||
|
||||
/// Gets an identifier unique to the window, provided by the underlying windowing system. This is
|
||||
/// not to be confused with [`Id`].
|
||||
pub fn get_raw_id<Message>(id: Id) -> Task<u64> {
|
||||
pub fn raw_id<Message>(id: Id) -> Task<u64> {
|
||||
task::oneshot(|channel| {
|
||||
crate::Action::Window(Action::GetRawId(id, channel))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//!
|
||||
//! pub fn main() -> iced::Result {
|
||||
//! iced::application(u64::default, update, view)
|
||||
//! .theme(|_| Theme::Dark)
|
||||
//! .theme(Theme::Dark)
|
||||
//! .centered()
|
||||
//! .run()
|
||||
//! }
|
||||
|
|
@ -35,7 +35,7 @@ use crate::shell;
|
|||
use crate::theme;
|
||||
use crate::window;
|
||||
use crate::{
|
||||
Element, Executor, Font, Result, Settings, Size, Subscription, Task,
|
||||
Element, Executor, Font, Result, Settings, Size, Subscription, Task, Theme,
|
||||
};
|
||||
|
||||
use iced_debug as debug;
|
||||
|
|
@ -75,14 +75,14 @@ pub use timed::timed;
|
|||
/// }
|
||||
/// ```
|
||||
pub fn application<State, Message, Theme, Renderer>(
|
||||
boot: impl Boot<State, Message>,
|
||||
update: impl Update<State, Message>,
|
||||
view: impl for<'a> View<'a, State, Message, Theme, Renderer>,
|
||||
boot: impl BootFn<State, Message>,
|
||||
update: impl UpdateFn<State, Message>,
|
||||
view: impl for<'a> ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
|
||||
where
|
||||
State: 'static,
|
||||
Message: program::Message + 'static,
|
||||
Theme: Default + theme::Base,
|
||||
Theme: theme::Base,
|
||||
Renderer: program::Renderer,
|
||||
{
|
||||
use std::marker::PhantomData;
|
||||
|
|
@ -101,11 +101,11 @@ where
|
|||
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||
where
|
||||
Message: program::Message + 'static,
|
||||
Theme: Default + theme::Base,
|
||||
Theme: theme::Base,
|
||||
Renderer: program::Renderer,
|
||||
Boot: self::Boot<State, Message>,
|
||||
Update: self::Update<State, Message>,
|
||||
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||
Boot: self::BootFn<State, Message>,
|
||||
Update: self::UpdateFn<State, Message>,
|
||||
View: for<'a> self::ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
{
|
||||
type State = State;
|
||||
type Message = Message;
|
||||
|
|
@ -320,10 +320,10 @@ impl<P: Program> Application<P> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets the [`Title`] of the [`Application`].
|
||||
/// Sets the title of the [`Application`].
|
||||
pub fn title(
|
||||
self,
|
||||
title: impl Title<P::State>,
|
||||
title: impl TitleFn<P::State>,
|
||||
) -> Application<
|
||||
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
|
||||
> {
|
||||
|
|
@ -355,13 +355,13 @@ impl<P: Program> Application<P> {
|
|||
/// Sets the theme logic of the [`Application`].
|
||||
pub fn theme(
|
||||
self,
|
||||
f: impl Fn(&P::State) -> P::Theme,
|
||||
f: impl ThemeFn<P::State, P::Theme>,
|
||||
) -> Application<
|
||||
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
|
||||
> {
|
||||
Application {
|
||||
raw: program::with_theme(self.raw, move |state, _window| {
|
||||
debug::hot(|| f(state))
|
||||
debug::hot(|| f.theme(state))
|
||||
}),
|
||||
settings: self.settings,
|
||||
window: self.window,
|
||||
|
|
@ -425,12 +425,12 @@ impl<P: Program> Application<P> {
|
|||
/// In practice, this means that [`application`] can both take
|
||||
/// simple functions like `State::default` and more advanced ones
|
||||
/// that return a [`Task`].
|
||||
pub trait Boot<State, Message> {
|
||||
pub trait BootFn<State, Message> {
|
||||
/// Initializes the [`Application`] state.
|
||||
fn boot(&self) -> (State, Task<Message>);
|
||||
}
|
||||
|
||||
impl<T, C, State, Message> Boot<State, Message> for T
|
||||
impl<T, C, State, Message> BootFn<State, Message> for T
|
||||
where
|
||||
T: Fn() -> C,
|
||||
C: IntoBoot<State, Message>,
|
||||
|
|
@ -464,18 +464,18 @@ impl<State, Message> IntoBoot<State, Message> for (State, Task<Message>) {
|
|||
/// any closure `Fn(&State) -> String`.
|
||||
///
|
||||
/// This trait allows the [`application`] builder to take any of them.
|
||||
pub trait Title<State> {
|
||||
pub trait TitleFn<State> {
|
||||
/// Produces the title of the [`Application`].
|
||||
fn title(&self, state: &State) -> String;
|
||||
}
|
||||
|
||||
impl<State> Title<State> for &'static str {
|
||||
impl<State> TitleFn<State> for &'static str {
|
||||
fn title(&self, _state: &State) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, State> Title<State> for T
|
||||
impl<T, State> TitleFn<State> for T
|
||||
where
|
||||
T: Fn(&State) -> String,
|
||||
{
|
||||
|
|
@ -488,18 +488,18 @@ where
|
|||
///
|
||||
/// This trait allows the [`application`] builder to take any closure that
|
||||
/// returns any `Into<Task<Message>>`.
|
||||
pub trait Update<State, Message> {
|
||||
pub trait UpdateFn<State, Message> {
|
||||
/// Processes the message and updates the state of the [`Application`].
|
||||
fn update(&self, state: &mut State, message: Message) -> Task<Message>;
|
||||
}
|
||||
|
||||
impl<State, Message> Update<State, Message> for () {
|
||||
impl<State, Message> UpdateFn<State, Message> for () {
|
||||
fn update(&self, _state: &mut State, _message: Message) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, State, Message, C> Update<State, Message> for T
|
||||
impl<T, State, Message, C> UpdateFn<State, Message> for T
|
||||
where
|
||||
T: Fn(&mut State, Message) -> C,
|
||||
C: Into<Task<Message>>,
|
||||
|
|
@ -513,13 +513,13 @@ where
|
|||
///
|
||||
/// This trait allows the [`application`] builder to take any closure that
|
||||
/// returns any `Into<Element<'_, Message>>`.
|
||||
pub trait View<'a, State, Message, Theme, Renderer> {
|
||||
pub trait ViewFn<'a, State, Message, Theme, Renderer> {
|
||||
/// Produces the widget of the [`Application`].
|
||||
fn view(&self, state: &'a State) -> Element<'a, Message, Theme, Renderer>;
|
||||
}
|
||||
|
||||
impl<'a, T, State, Message, Theme, Renderer, Widget>
|
||||
View<'a, State, Message, Theme, Renderer> for T
|
||||
ViewFn<'a, State, Message, Theme, Renderer> for T
|
||||
where
|
||||
T: Fn(&'a State) -> Widget,
|
||||
State: 'static,
|
||||
|
|
@ -529,3 +529,35 @@ where
|
|||
self(state).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The theme logic of some [`Application`].
|
||||
///
|
||||
/// Any implementors of this trait can be provided as an argument to
|
||||
/// [`Application::theme`].
|
||||
///
|
||||
/// `iced` provides two implementors:
|
||||
/// - the built-in [`Theme`] itself
|
||||
/// - and any `Fn(&State) -> impl Into<Option<Theme>>`.
|
||||
pub trait ThemeFn<State, Theme> {
|
||||
/// Returns the theme of the [`Application`] for the current state.
|
||||
///
|
||||
/// If `None` is returned, `iced` will try to use a theme that
|
||||
/// matches the system color scheme.
|
||||
fn theme(&self, state: &State) -> Option<Theme>;
|
||||
}
|
||||
|
||||
impl<State> ThemeFn<State, Theme> for Theme {
|
||||
fn theme(&self, _state: &State) -> Option<Theme> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, State, Theme> ThemeFn<State, Theme> for F
|
||||
where
|
||||
F: Fn(&State) -> T,
|
||||
T: Into<Option<Theme>>,
|
||||
{
|
||||
fn theme(&self, state: &State) -> Option<Theme> {
|
||||
(self)(state).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//! An [`Application`] that receives an [`Instant`] in update logic.
|
||||
use crate::application::{Application, Boot, View};
|
||||
use crate::application::{Application, BootFn, ViewFn};
|
||||
use crate::program;
|
||||
use crate::theme;
|
||||
use crate::time::Instant;
|
||||
|
|
@ -20,17 +20,17 @@ use iced_debug as debug;
|
|||
///
|
||||
/// [`comet`]: https://github.com/iced-rs/comet
|
||||
pub fn timed<State, Message, Theme, Renderer>(
|
||||
boot: impl Boot<State, Message>,
|
||||
update: impl Update<State, Message>,
|
||||
boot: impl BootFn<State, Message>,
|
||||
update: impl UpdateFn<State, Message>,
|
||||
subscription: impl Fn(&State) -> Subscription<Message>,
|
||||
view: impl for<'a> View<'a, State, Message, Theme, Renderer>,
|
||||
view: impl for<'a> ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
) -> Application<
|
||||
impl Program<State = State, Message = (Message, Instant), Theme = Theme>,
|
||||
>
|
||||
where
|
||||
State: 'static,
|
||||
Message: program::Message + 'static,
|
||||
Theme: Default + theme::Base + 'static,
|
||||
Theme: theme::Base + 'static,
|
||||
Renderer: program::Renderer + 'static,
|
||||
{
|
||||
use std::marker::PhantomData;
|
||||
|
|
@ -69,12 +69,12 @@ where
|
|||
>
|
||||
where
|
||||
Message: program::Message + 'static,
|
||||
Theme: Default + theme::Base + 'static,
|
||||
Theme: theme::Base + 'static,
|
||||
Renderer: program::Renderer + 'static,
|
||||
Boot: self::Boot<State, Message>,
|
||||
Update: self::Update<State, Message>,
|
||||
Boot: self::BootFn<State, Message>,
|
||||
Update: self::UpdateFn<State, Message>,
|
||||
Subscription: Fn(&State) -> self::Subscription<Message>,
|
||||
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||
View: for<'a> self::ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
{
|
||||
type State = State;
|
||||
type Message = (Message, Instant);
|
||||
|
|
@ -148,9 +148,9 @@ where
|
|||
|
||||
/// The update logic of some timed [`Application`].
|
||||
///
|
||||
/// This is like [`application::Update`](super::Update),
|
||||
/// This is like [`application::UpdateFn`](super::UpdateFn),
|
||||
/// but it also takes an [`Instant`].
|
||||
pub trait Update<State, Message> {
|
||||
pub trait UpdateFn<State, Message> {
|
||||
/// Processes the message and updates the state of the [`Application`].
|
||||
fn update(
|
||||
&self,
|
||||
|
|
@ -160,7 +160,7 @@ pub trait Update<State, Message> {
|
|||
) -> impl Into<Task<Message>>;
|
||||
}
|
||||
|
||||
impl<State, Message> Update<State, Message> for () {
|
||||
impl<State, Message> UpdateFn<State, Message> for () {
|
||||
fn update(
|
||||
&self,
|
||||
_state: &mut State,
|
||||
|
|
@ -170,7 +170,7 @@ impl<State, Message> Update<State, Message> for () {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, State, Message, C> Update<State, Message> for T
|
||||
impl<T, State, Message, C> UpdateFn<State, Message> for T
|
||||
where
|
||||
T: Fn(&mut State, Message, Instant) -> C,
|
||||
C: Into<Task<Message>>,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ use crate::program::{self, Program};
|
|||
use crate::shell;
|
||||
use crate::theme;
|
||||
use crate::window;
|
||||
use crate::{Element, Executor, Font, Result, Settings, Subscription, Task};
|
||||
use crate::{
|
||||
Element, Executor, Font, Result, Settings, Subscription, Task, Theme,
|
||||
};
|
||||
|
||||
use iced_debug as debug;
|
||||
|
||||
|
|
@ -21,14 +23,14 @@ use std::borrow::Cow;
|
|||
///
|
||||
/// [`exit`]: crate::exit
|
||||
pub fn daemon<State, Message, Theme, Renderer>(
|
||||
boot: impl application::Boot<State, Message>,
|
||||
update: impl application::Update<State, Message>,
|
||||
view: impl for<'a> View<'a, State, Message, Theme, Renderer>,
|
||||
boot: impl application::BootFn<State, Message>,
|
||||
update: impl application::UpdateFn<State, Message>,
|
||||
view: impl for<'a> ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>>
|
||||
where
|
||||
State: 'static,
|
||||
Message: program::Message + 'static,
|
||||
Theme: Default + theme::Base,
|
||||
Theme: theme::Base,
|
||||
Renderer: program::Renderer,
|
||||
{
|
||||
use std::marker::PhantomData;
|
||||
|
|
@ -47,11 +49,11 @@ where
|
|||
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||
where
|
||||
Message: program::Message + 'static,
|
||||
Theme: Default + theme::Base,
|
||||
Theme: theme::Base,
|
||||
Renderer: program::Renderer,
|
||||
Boot: application::Boot<State, Message>,
|
||||
Update: application::Update<State, Message>,
|
||||
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||
Boot: application::BootFn<State, Message>,
|
||||
Update: application::UpdateFn<State, Message>,
|
||||
View: for<'a> self::ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
{
|
||||
type State = State;
|
||||
type Message = Message;
|
||||
|
|
@ -169,10 +171,10 @@ impl<P: Program> Daemon<P> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Title`] of the [`Daemon`].
|
||||
/// Sets the title of the [`Daemon`].
|
||||
pub fn title(
|
||||
self,
|
||||
title: impl Title<P::State>,
|
||||
title: impl TitleFn<P::State>,
|
||||
) -> Daemon<
|
||||
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
|
||||
> {
|
||||
|
|
@ -202,13 +204,13 @@ impl<P: Program> Daemon<P> {
|
|||
/// Sets the theme logic of the [`Daemon`].
|
||||
pub fn theme(
|
||||
self,
|
||||
f: impl Fn(&P::State, window::Id) -> P::Theme,
|
||||
f: impl ThemeFn<P::State, P::Theme>,
|
||||
) -> Daemon<
|
||||
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
|
||||
> {
|
||||
Daemon {
|
||||
raw: program::with_theme(self.raw, move |state, window| {
|
||||
debug::hot(|| f(state, window))
|
||||
debug::hot(|| f.theme(state, window))
|
||||
}),
|
||||
settings: self.settings,
|
||||
}
|
||||
|
|
@ -266,18 +268,18 @@ impl<P: Program> Daemon<P> {
|
|||
/// any closure `Fn(&State, window::Id) -> String`.
|
||||
///
|
||||
/// This trait allows the [`daemon`] builder to take any of them.
|
||||
pub trait Title<State> {
|
||||
pub trait TitleFn<State> {
|
||||
/// Produces the title of the [`Daemon`].
|
||||
fn title(&self, state: &State, window: window::Id) -> String;
|
||||
}
|
||||
|
||||
impl<State> Title<State> for &'static str {
|
||||
impl<State> TitleFn<State> for &'static str {
|
||||
fn title(&self, _state: &State, _window: window::Id) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, State> Title<State> for T
|
||||
impl<T, State> TitleFn<State> for T
|
||||
where
|
||||
T: Fn(&State, window::Id) -> String,
|
||||
{
|
||||
|
|
@ -290,7 +292,7 @@ where
|
|||
///
|
||||
/// This trait allows the [`daemon`] builder to take any closure that
|
||||
/// returns any `Into<Element<'_, Message>>`.
|
||||
pub trait View<'a, State, Message, Theme, Renderer> {
|
||||
pub trait ViewFn<'a, State, Message, Theme, Renderer> {
|
||||
/// Produces the widget of the [`Daemon`].
|
||||
fn view(
|
||||
&self,
|
||||
|
|
@ -300,7 +302,7 @@ pub trait View<'a, State, Message, Theme, Renderer> {
|
|||
}
|
||||
|
||||
impl<'a, T, State, Message, Theme, Renderer, Widget>
|
||||
View<'a, State, Message, Theme, Renderer> for T
|
||||
ViewFn<'a, State, Message, Theme, Renderer> for T
|
||||
where
|
||||
T: Fn(&'a State, window::Id) -> Widget,
|
||||
State: 'static,
|
||||
|
|
@ -314,3 +316,35 @@ where
|
|||
self(state, window).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The theme logic of some [`Daemon`].
|
||||
///
|
||||
/// Any implementors of this trait can be provided as an argument to
|
||||
/// [`Daemon::theme`].
|
||||
///
|
||||
/// `iced` provides two implementors:
|
||||
/// - the built-in [`Theme`] itself
|
||||
/// - and any `Fn(&State, window::Id) -> impl Into<Option<Theme>>`.
|
||||
pub trait ThemeFn<State, Theme> {
|
||||
/// Returns the theme of the [`Daemon`] for the current state and window.
|
||||
///
|
||||
/// If `None` is returned, `iced` will try to use a theme that
|
||||
/// matches the system color scheme.
|
||||
fn theme(&self, state: &State, window: window::Id) -> Option<Theme>;
|
||||
}
|
||||
|
||||
impl<State> ThemeFn<State, Theme> for Theme {
|
||||
fn theme(&self, _state: &State, _window: window::Id) -> Option<Theme> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, State, Theme> ThemeFn<State, Theme> for F
|
||||
where
|
||||
F: Fn(&State, window::Id) -> T,
|
||||
T: Into<Option<Theme>>,
|
||||
{
|
||||
fn theme(&self, state: &State, window: window::Id) -> Option<Theme> {
|
||||
(self)(state, window).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
src/lib.rs
13
src/lib.rs
|
|
@ -587,11 +587,12 @@ pub mod mouse {
|
|||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
pub mod system {
|
||||
//! Retrieve system information.
|
||||
pub use crate::runtime::system::Information;
|
||||
pub use crate::shell::system::*;
|
||||
pub use crate::runtime::system::{theme, theme_changes};
|
||||
|
||||
#[cfg(feature = "sysinfo")]
|
||||
pub use crate::runtime::system::{Information, information};
|
||||
}
|
||||
|
||||
pub mod overlay {
|
||||
|
|
@ -691,14 +692,14 @@ pub type Result = std::result::Result<(), Error>;
|
|||
/// }
|
||||
/// ```
|
||||
pub fn run<State, Message, Theme, Renderer>(
|
||||
update: impl application::Update<State, Message> + 'static,
|
||||
view: impl for<'a> application::View<'a, State, Message, Theme, Renderer>
|
||||
update: impl application::UpdateFn<State, Message> + 'static,
|
||||
view: impl for<'a> application::ViewFn<'a, State, Message, Theme, Renderer>
|
||||
+ 'static,
|
||||
) -> Result
|
||||
where
|
||||
State: Default + 'static,
|
||||
Message: program::Message + 'static,
|
||||
Theme: Default + theme::Base + 'static,
|
||||
Theme: theme::Base + 'static,
|
||||
Renderer: program::Renderer + 'static,
|
||||
{
|
||||
application(State::default, update, view).run()
|
||||
|
|
|
|||
|
|
@ -494,10 +494,13 @@ impl Snapshot {
|
|||
|
||||
if path.exists() {
|
||||
let file = fs::File::open(&path)?;
|
||||
let decoder = png::Decoder::new(file);
|
||||
let decoder = png::Decoder::new(io::BufReader::new(file));
|
||||
|
||||
let mut reader = decoder.read_info()?;
|
||||
let mut bytes = vec![0; reader.output_buffer_size()];
|
||||
let n = reader
|
||||
.output_buffer_size()
|
||||
.expect("snapshot should fit in memory");
|
||||
let mut bytes = vec![0; n];
|
||||
let info = reader.next_frame(&mut bytes)?;
|
||||
|
||||
Ok(self.screenshot.bytes == bytes[..info.buffer_size()])
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ impl crate::graphics::Compositor for Compositor {
|
|||
surface.layer_stack.clear();
|
||||
}
|
||||
|
||||
fn fetch_information(&self) -> Information {
|
||||
fn information(&self) -> Information {
|
||||
Information {
|
||||
adapter: String::from("CPU"),
|
||||
backend: String::from("tiny-skia"),
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ impl graphics::Compositor for Compositor {
|
|||
);
|
||||
}
|
||||
|
||||
fn fetch_information(&self) -> compositor::Information {
|
||||
fn information(&self) -> compositor::Information {
|
||||
let information = self.adapter.get_info();
|
||||
|
||||
compositor::Information {
|
||||
|
|
|
|||
|
|
@ -2063,7 +2063,7 @@ where
|
|||
|
||||
/// A widget that applies any `Theme` to its contents.
|
||||
pub fn themer<'a, Message, OldTheme, NewTheme, Renderer>(
|
||||
new_theme: NewTheme,
|
||||
to_theme: impl Fn(&OldTheme) -> NewTheme,
|
||||
content: impl Into<Element<'a, Message, NewTheme, Renderer>>,
|
||||
) -> Themer<
|
||||
'a,
|
||||
|
|
@ -2077,7 +2077,7 @@ where
|
|||
Renderer: core::Renderer,
|
||||
NewTheme: Clone,
|
||||
{
|
||||
Themer::new(move |_| new_theme.clone(), content)
|
||||
Themer::new(to_theme, content)
|
||||
}
|
||||
|
||||
/// Creates a [`PaneGrid`] with the given [`pane_grid::State`] and view function.
|
||||
|
|
|
|||
|
|
@ -16,13 +16,14 @@ workspace = true
|
|||
[features]
|
||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
debug = ["iced_debug/enable"]
|
||||
system = ["sysinfo"]
|
||||
sysinfo = ["dep:sysinfo"]
|
||||
program = []
|
||||
x11 = ["winit/x11"]
|
||||
wayland = ["winit/wayland"]
|
||||
wayland-dlopen = ["winit/wayland-dlopen"]
|
||||
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
||||
unconditional-rendering = []
|
||||
linux-theme-detection = ["dep:mundy", "mundy/async-io", "mundy/color-scheme"]
|
||||
|
||||
[dependencies]
|
||||
iced_debug.workspace = true
|
||||
|
|
@ -42,3 +43,7 @@ sysinfo.optional = true
|
|||
web-sys.workspace = true
|
||||
web-sys.features = ["Document", "Window", "HtmlCanvasElement"]
|
||||
wasm-bindgen-futures.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
mundy.workspace = true
|
||||
mundy.optional = true
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
use crate::core::input_method;
|
||||
use crate::core::keyboard;
|
||||
use crate::core::mouse;
|
||||
use crate::core::theme;
|
||||
use crate::core::touch;
|
||||
use crate::core::window;
|
||||
use crate::core::{Event, Point, Size};
|
||||
|
|
@ -322,7 +323,7 @@ pub fn window_event(
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`window::Level`] to a [`winit`] window level.
|
||||
/// Converts a [`window::Level`] into a [`winit`] window level.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn window_level(level: window::Level) -> winit::window::WindowLevel {
|
||||
|
|
@ -335,7 +336,7 @@ pub fn window_level(level: window::Level) -> winit::window::WindowLevel {
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`window::Position`] to a [`winit`] logical position for a given monitor.
|
||||
/// Converts a [`window::Position`] into a [`winit`] logical position for a given monitor.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn position(
|
||||
|
|
@ -407,7 +408,7 @@ pub fn position(
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`window::Mode`] to a [`winit`] fullscreen mode.
|
||||
/// Converts a [`window::Mode`] into a [`winit`] fullscreen mode.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn fullscreen(
|
||||
|
|
@ -422,7 +423,7 @@ pub fn fullscreen(
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`window::Mode`] to a visibility flag.
|
||||
/// Converts a [`window::Mode`] into a visibility flag.
|
||||
pub fn visible(mode: window::Mode) -> bool {
|
||||
match mode {
|
||||
window::Mode::Windowed | window::Mode::Fullscreen => true,
|
||||
|
|
@ -430,7 +431,7 @@ pub fn visible(mode: window::Mode) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`winit`] fullscreen mode to a [`window::Mode`].
|
||||
/// Converts a [`winit`] fullscreen mode into a [`window::Mode`].
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn mode(mode: Option<winit::window::Fullscreen>) -> window::Mode {
|
||||
|
|
@ -440,7 +441,28 @@ pub fn mode(mode: Option<winit::window::Fullscreen>) -> window::Mode {
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`mouse::Interaction`] to a [`winit`] cursor icon.
|
||||
/// Converts a [`winit`] window theme into a [`theme::Mode`].
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn theme_mode(theme: winit::window::Theme) -> theme::Mode {
|
||||
match theme {
|
||||
winit::window::Theme::Light => theme::Mode::Light,
|
||||
winit::window::Theme::Dark => theme::Mode::Dark,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`theme::Mode`] into a window theme.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn window_theme(mode: theme::Mode) -> Option<winit::window::Theme> {
|
||||
match mode {
|
||||
theme::Mode::None => None,
|
||||
theme::Mode::Light => Some(winit::window::Theme::Light),
|
||||
theme::Mode::Dark => Some(winit::window::Theme::Dark),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`mouse::Interaction`] into a [`winit`] cursor icon.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn mouse_interaction(
|
||||
|
|
@ -511,7 +533,7 @@ pub fn modifiers(
|
|||
result
|
||||
}
|
||||
|
||||
/// Converts a physical cursor position to a logical `Point`.
|
||||
/// Converts a physical cursor position into a logical `Point`.
|
||||
pub fn cursor_position(
|
||||
position: winit::dpi::PhysicalPosition<f64>,
|
||||
scale_factor: f32,
|
||||
|
|
@ -1182,7 +1204,7 @@ pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {
|
|||
winit::window::Icon::from_rgba(pixels, size.width, size.height).ok()
|
||||
}
|
||||
|
||||
/// Convertions some [`input_method::Purpose`] to its `winit` counterpart.
|
||||
/// Converts some [`input_method::Purpose`] into its `winit` counterpart.
|
||||
pub fn ime_purpose(
|
||||
purpose: input_method::Purpose,
|
||||
) -> winit::window::ImePurpose {
|
||||
|
|
|
|||
181
winit/src/lib.rs
181
winit/src/lib.rs
|
|
@ -29,9 +29,6 @@ pub use winit;
|
|||
pub mod clipboard;
|
||||
pub mod conversion;
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
pub mod system;
|
||||
|
||||
mod error;
|
||||
mod proxy;
|
||||
mod window;
|
||||
|
|
@ -53,6 +50,7 @@ use crate::futures::futures::{Future, StreamExt};
|
|||
use crate::futures::subscription;
|
||||
use crate::futures::{Executor, Runtime};
|
||||
use crate::graphics::{Compositor, compositor};
|
||||
use crate::runtime::system;
|
||||
use crate::runtime::user_interface::{self, UserInterface};
|
||||
use crate::runtime::{Action, Task};
|
||||
|
||||
|
|
@ -111,7 +109,7 @@ where
|
|||
|
||||
let (_id, open) = runtime::window::open(window_settings);
|
||||
|
||||
open.then(move |_| task.take().unwrap_or(Task::none()))
|
||||
open.then(move |_| task.take().unwrap_or_else(Task::none))
|
||||
} else {
|
||||
task
|
||||
};
|
||||
|
|
@ -126,6 +124,7 @@ where
|
|||
|
||||
let (event_sender, event_receiver) = mpsc::unbounded();
|
||||
let (control_sender, control_receiver) = mpsc::unbounded();
|
||||
let (system_theme_sender, system_theme_receiver) = oneshot::channel();
|
||||
|
||||
let instance = Box::pin(run_instance::<P>(
|
||||
program,
|
||||
|
|
@ -136,6 +135,7 @@ where
|
|||
is_daemon,
|
||||
graphics_settings,
|
||||
settings.fonts,
|
||||
system_theme_receiver,
|
||||
));
|
||||
|
||||
let context = task::Context::from_waker(task::noop_waker_ref());
|
||||
|
|
@ -147,6 +147,7 @@ where
|
|||
sender: mpsc::UnboundedSender<Event<Action<Message>>>,
|
||||
receiver: mpsc::UnboundedReceiver<Control>,
|
||||
error: Option<Error>,
|
||||
system_theme: Option<oneshot::Sender<theme::Mode>>,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
canvas: Option<web_sys::HtmlCanvasElement>,
|
||||
|
|
@ -159,6 +160,7 @@ where
|
|||
sender: event_sender,
|
||||
receiver: control_receiver,
|
||||
error: None,
|
||||
system_theme: Some(system_theme_sender),
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
canvas: None,
|
||||
|
|
@ -172,10 +174,15 @@ where
|
|||
Message: std::fmt::Debug,
|
||||
F: Future<Output = ()>,
|
||||
{
|
||||
fn resumed(
|
||||
&mut self,
|
||||
_event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
) {
|
||||
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
if let Some(sender) = self.system_theme.take() {
|
||||
let _ = sender.send(
|
||||
event_loop
|
||||
.system_theme()
|
||||
.map(conversion::theme_mode)
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn new_events(
|
||||
|
|
@ -498,6 +505,7 @@ async fn run_instance<P>(
|
|||
is_daemon: bool,
|
||||
graphics_settings: graphics::Settings,
|
||||
default_fonts: Vec<Cow<'static, [u8]>>,
|
||||
mut _system_theme: oneshot::Receiver<theme::Mode>,
|
||||
) where
|
||||
P: Program + 'static,
|
||||
P::Theme: theme::Base,
|
||||
|
|
@ -517,6 +525,38 @@ async fn run_instance<P>(
|
|||
let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
|
||||
let mut clipboard = Clipboard::unconnected();
|
||||
|
||||
#[cfg(all(feature = "linux-theme-detection", target_os = "linux"))]
|
||||
let mut system_theme = {
|
||||
let to_mode = |color_scheme| match color_scheme {
|
||||
mundy::ColorScheme::NoPreference => theme::Mode::None,
|
||||
mundy::ColorScheme::Light => theme::Mode::Light,
|
||||
mundy::ColorScheme::Dark => theme::Mode::Dark,
|
||||
};
|
||||
|
||||
runtime.run(
|
||||
mundy::Preferences::stream(mundy::Interest::ColorScheme)
|
||||
.map(move |preferences| {
|
||||
Action::System(system::Action::NotifyTheme(to_mode(
|
||||
preferences.color_scheme,
|
||||
)))
|
||||
})
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
mundy::Preferences::once_blocking(
|
||||
mundy::Interest::ColorScheme,
|
||||
core::time::Duration::from_millis(200),
|
||||
)
|
||||
.map(|preferences| to_mode(preferences.color_scheme))
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
#[cfg(not(all(feature = "linux-theme-detection", target_os = "linux")))]
|
||||
let mut system_theme =
|
||||
_system_theme.try_recv().ok().flatten().unwrap_or_default();
|
||||
|
||||
log::info!("System theme: {system_theme:?}");
|
||||
|
||||
loop {
|
||||
// Empty the queue if possible
|
||||
let event = if let Ok(event) = event_receiver.try_next() {
|
||||
|
|
@ -595,14 +635,7 @@ async fn run_instance<P>(
|
|||
}
|
||||
}
|
||||
|
||||
debug::theme_changed(|| {
|
||||
if window_manager.is_empty() {
|
||||
theme::Base::palette(&program.theme(id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let is_first = window_manager.is_empty();
|
||||
let window = window_manager.insert(
|
||||
id,
|
||||
window,
|
||||
|
|
@ -611,8 +644,21 @@ async fn run_instance<P>(
|
|||
.as_mut()
|
||||
.expect("Compositor must be initialized"),
|
||||
exit_on_close_request,
|
||||
system_theme,
|
||||
);
|
||||
|
||||
window.raw.set_theme(conversion::window_theme(
|
||||
window.state.theme_mode(),
|
||||
));
|
||||
|
||||
debug::theme_changed(|| {
|
||||
if is_first {
|
||||
theme::Base::palette(window.state.theme())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let logical_size = window.state.logical_size();
|
||||
|
||||
let _ = user_interfaces.insert(
|
||||
|
|
@ -695,6 +741,7 @@ async fn run_instance<P>(
|
|||
run_action(
|
||||
action,
|
||||
&program,
|
||||
&mut runtime,
|
||||
&mut compositor,
|
||||
&mut events,
|
||||
&mut messages,
|
||||
|
|
@ -704,6 +751,7 @@ async fn run_instance<P>(
|
|||
&mut window_manager,
|
||||
&mut ui_caches,
|
||||
&mut is_window_opening,
|
||||
&mut system_theme,
|
||||
);
|
||||
actions += 1;
|
||||
}
|
||||
|
|
@ -862,11 +910,24 @@ async fn run_instance<P>(
|
|||
continue;
|
||||
};
|
||||
|
||||
if matches!(
|
||||
window_event,
|
||||
winit::event::WindowEvent::Resized(_)
|
||||
) {
|
||||
window.raw.request_redraw();
|
||||
match window_event {
|
||||
winit::event::WindowEvent::Resized(_) => {
|
||||
window.raw.request_redraw();
|
||||
}
|
||||
winit::event::WindowEvent::ThemeChanged(theme) => {
|
||||
let mode = conversion::theme_mode(theme);
|
||||
|
||||
if mode != system_theme {
|
||||
system_theme = mode;
|
||||
|
||||
runtime.broadcast(
|
||||
subscription::Event::SystemThemeChanged(
|
||||
mode,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if matches!(
|
||||
|
|
@ -879,6 +940,7 @@ async fn run_instance<P>(
|
|||
id,
|
||||
)),
|
||||
&program,
|
||||
&mut runtime,
|
||||
&mut compositor,
|
||||
&mut events,
|
||||
&mut messages,
|
||||
|
|
@ -888,9 +950,14 @@ async fn run_instance<P>(
|
|||
&mut window_manager,
|
||||
&mut ui_caches,
|
||||
&mut is_window_opening,
|
||||
&mut system_theme,
|
||||
);
|
||||
} else {
|
||||
window.state.update(&window.raw, &window_event);
|
||||
window.state.update(
|
||||
&program,
|
||||
&window.raw,
|
||||
&window_event,
|
||||
);
|
||||
|
||||
if let Some(event) = conversion::window_event(
|
||||
window_event,
|
||||
|
|
@ -1095,6 +1162,7 @@ fn update<P: Program, E: Executor>(
|
|||
fn run_action<'a, P, C>(
|
||||
action: Action<P::Message>,
|
||||
program: &'a program::Instance<P>,
|
||||
runtime: &mut Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
|
||||
compositor: &mut Option<C>,
|
||||
events: &mut Vec<(window::Id, core::Event)>,
|
||||
messages: &mut Vec<P::Message>,
|
||||
|
|
@ -1107,13 +1175,13 @@ fn run_action<'a, P, C>(
|
|||
window_manager: &mut WindowManager<P, C>,
|
||||
ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>,
|
||||
is_window_opening: &mut bool,
|
||||
system_theme: &mut theme::Mode,
|
||||
) where
|
||||
P: Program,
|
||||
C: Compositor<Renderer = P::Renderer> + 'static,
|
||||
P::Theme: theme::Base,
|
||||
{
|
||||
use crate::runtime::clipboard;
|
||||
use crate::runtime::system;
|
||||
use crate::runtime::window;
|
||||
|
||||
match action {
|
||||
|
|
@ -1421,21 +1489,44 @@ fn run_action<'a, P, C>(
|
|||
}
|
||||
},
|
||||
Action::System(action) => match action {
|
||||
system::Action::QueryInformation(_channel) => {
|
||||
#[cfg(feature = "system")]
|
||||
system::Action::GetInformation(_channel) => {
|
||||
#[cfg(feature = "sysinfo")]
|
||||
{
|
||||
if let Some(compositor) = compositor {
|
||||
let graphics_info = compositor.fetch_information();
|
||||
let graphics_info = compositor.information();
|
||||
|
||||
let _ = std::thread::spawn(move || {
|
||||
let information =
|
||||
crate::system::information(graphics_info);
|
||||
let information = system_information(graphics_info);
|
||||
|
||||
let _ = _channel.send(information);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
system::Action::GetTheme(channel) => {
|
||||
let _ = channel.send(*system_theme);
|
||||
}
|
||||
system::Action::NotifyTheme(mode) => {
|
||||
if mode != *system_theme {
|
||||
*system_theme = mode;
|
||||
|
||||
runtime.broadcast(subscription::Event::SystemThemeChanged(
|
||||
mode,
|
||||
));
|
||||
}
|
||||
|
||||
let Some(theme) = conversion::window_theme(mode) else {
|
||||
return;
|
||||
};
|
||||
|
||||
for (_id, window) in window_manager.iter_mut() {
|
||||
window.state.update(
|
||||
program,
|
||||
&window.raw,
|
||||
&winit::event::WindowEvent::ThemeChanged(theme),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
Action::Widget(operation) => {
|
||||
let mut current_operation = Some(operation);
|
||||
|
|
@ -1544,3 +1635,37 @@ pub fn user_force_quit(
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sysinfo")]
|
||||
fn system_information(
|
||||
graphics: compositor::Information,
|
||||
) -> system::Information {
|
||||
use sysinfo::{Process, System};
|
||||
|
||||
let mut system = System::new_all();
|
||||
system.refresh_all();
|
||||
|
||||
let cpu_brand = system
|
||||
.cpus()
|
||||
.first()
|
||||
.map(|cpu| cpu.brand().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let memory_used = sysinfo::get_current_pid()
|
||||
.and_then(|pid| system.process(pid).ok_or("Process not found"))
|
||||
.map(Process::memory)
|
||||
.ok();
|
||||
|
||||
system::Information {
|
||||
system_name: System::name(),
|
||||
system_kernel: System::kernel_version(),
|
||||
system_version: System::long_os_version(),
|
||||
system_short_version: System::os_version(),
|
||||
cpu_brand,
|
||||
cpu_cores: system.physical_core_count(),
|
||||
memory_total: system.total_memory(),
|
||||
memory_used,
|
||||
graphics_adapter: graphics.adapter,
|
||||
graphics_backend: graphics.backend,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
//! Access the native system.
|
||||
use crate::graphics::compositor;
|
||||
use crate::runtime::system::{Action, Information};
|
||||
use crate::runtime::{self, Task};
|
||||
|
||||
/// Query for available system information.
|
||||
pub fn fetch_information() -> Task<Information> {
|
||||
runtime::task::oneshot(|channel| {
|
||||
runtime::Action::System(Action::QueryInformation(channel))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn information(
|
||||
graphics_info: compositor::Information,
|
||||
) -> Information {
|
||||
use sysinfo::{Process, System};
|
||||
let mut system = System::new_all();
|
||||
system.refresh_all();
|
||||
|
||||
let cpu_brand = system
|
||||
.cpus()
|
||||
.first()
|
||||
.map(|cpu| cpu.brand().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let memory_used = sysinfo::get_current_pid()
|
||||
.and_then(|pid| system.process(pid).ok_or("Process not found"))
|
||||
.map(Process::memory)
|
||||
.ok();
|
||||
|
||||
Information {
|
||||
system_name: System::name(),
|
||||
system_kernel: System::kernel_version(),
|
||||
system_version: System::long_os_version(),
|
||||
system_short_version: System::os_version(),
|
||||
cpu_brand,
|
||||
cpu_cores: system.physical_core_count(),
|
||||
memory_total: system.total_memory(),
|
||||
memory_used,
|
||||
graphics_adapter: graphics_info.adapter,
|
||||
graphics_backend: graphics_info.backend,
|
||||
}
|
||||
}
|
||||
|
|
@ -55,8 +55,9 @@ where
|
|||
program: &program::Instance<P>,
|
||||
compositor: &mut C,
|
||||
exit_on_close_request: bool,
|
||||
system_theme: theme::Mode,
|
||||
) -> &mut Window<P, C> {
|
||||
let state = State::new(program, id, &window);
|
||||
let state = State::new(program, id, &window, system_theme);
|
||||
let viewport_version = state.viewport_version();
|
||||
let physical_size = state.physical_size();
|
||||
let surface = compositor.create_surface(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use winit::window::Window;
|
|||
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
/// The state of a multi-windowed [`Program`].
|
||||
/// The state of the window of a [`Program`].
|
||||
pub struct State<P: Program>
|
||||
where
|
||||
P::Theme: theme::Base,
|
||||
|
|
@ -20,7 +20,9 @@ where
|
|||
viewport_version: u64,
|
||||
cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
|
||||
modifiers: winit::keyboard::ModifiersState,
|
||||
theme: P::Theme,
|
||||
theme: Option<P::Theme>,
|
||||
theme_mode: theme::Mode,
|
||||
default_theme: P::Theme,
|
||||
style: theme::Style,
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +31,7 @@ where
|
|||
P::Theme: theme::Base,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("multi_window::State")
|
||||
f.debug_struct("window::State")
|
||||
.field("title", &self.title)
|
||||
.field("scale_factor", &self.scale_factor)
|
||||
.field("viewport", &self.viewport)
|
||||
|
|
@ -49,11 +51,15 @@ where
|
|||
program: &program::Instance<P>,
|
||||
window_id: window::Id,
|
||||
window: &Window,
|
||||
system_theme: theme::Mode,
|
||||
) -> Self {
|
||||
let title = program.title(window_id);
|
||||
let scale_factor = program.scale_factor(window_id);
|
||||
let theme = program.theme(window_id);
|
||||
let style = program.style(&theme);
|
||||
let theme_mode =
|
||||
theme.as_ref().map(theme::Base::mode).unwrap_or_default();
|
||||
let default_theme = <P::Theme as theme::Base>::default(system_theme);
|
||||
let style = program.style(theme.as_ref().unwrap_or(&default_theme));
|
||||
|
||||
let viewport = {
|
||||
let physical_size = window.inner_size();
|
||||
|
|
@ -72,6 +78,8 @@ where
|
|||
cursor_position: None,
|
||||
modifiers: winit::keyboard::ModifiersState::default(),
|
||||
theme,
|
||||
theme_mode,
|
||||
default_theme,
|
||||
style,
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +131,12 @@ where
|
|||
|
||||
/// Returns the current theme of the [`State`].
|
||||
pub fn theme(&self) -> &P::Theme {
|
||||
&self.theme
|
||||
self.theme.as_ref().unwrap_or(&self.default_theme)
|
||||
}
|
||||
|
||||
/// Returns the current [`theme::Mode`] of the [`State`].
|
||||
pub fn theme_mode(&self) -> theme::Mode {
|
||||
self.theme_mode
|
||||
}
|
||||
|
||||
/// Returns the current background [`Color`] of the [`State`].
|
||||
|
|
@ -137,7 +150,12 @@ where
|
|||
}
|
||||
|
||||
/// Processes the provided window event and updates the [`State`] accordingly.
|
||||
pub fn update(&mut self, window: &Window, event: &WindowEvent) {
|
||||
pub fn update(
|
||||
&mut self,
|
||||
program: &program::Instance<P>,
|
||||
window: &Window,
|
||||
event: &WindowEvent,
|
||||
) {
|
||||
match event {
|
||||
WindowEvent::Resized(new_size) => {
|
||||
let size = Size::new(new_size.width, new_size.height);
|
||||
|
|
@ -174,6 +192,16 @@ where
|
|||
WindowEvent::ModifiersChanged(new_modifiers) => {
|
||||
self.modifiers = new_modifiers.state();
|
||||
}
|
||||
WindowEvent::ThemeChanged(theme) => {
|
||||
self.default_theme = <P::Theme as theme::Base>::default(
|
||||
conversion::theme_mode(*theme),
|
||||
);
|
||||
|
||||
if self.theme.is_none() {
|
||||
self.style = program.style(&self.default_theme);
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -182,7 +210,7 @@ where
|
|||
/// window.
|
||||
///
|
||||
/// Normally, a [`Program`] should be synchronized with its [`State`]
|
||||
/// and window after calling [`State::update`].
|
||||
/// and window after calling [`Program::update`].
|
||||
pub fn synchronize(
|
||||
&mut self,
|
||||
program: &program::Instance<P>,
|
||||
|
|
@ -217,6 +245,45 @@ where
|
|||
|
||||
// Update theme and appearance
|
||||
self.theme = program.theme(window_id);
|
||||
self.style = program.style(&self.theme);
|
||||
self.style = program.style(self.theme());
|
||||
|
||||
let new_mode = self
|
||||
.theme
|
||||
.as_ref()
|
||||
.map(theme::Base::mode)
|
||||
.unwrap_or_default();
|
||||
|
||||
if self.theme_mode != new_mode {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
window.set_theme(conversion::window_theme(new_mode));
|
||||
|
||||
// Assume the old mode matches the system one
|
||||
// We will be notified otherwise
|
||||
if new_mode == theme::Mode::None {
|
||||
self.default_theme =
|
||||
<P::Theme as theme::Base>::default(self.theme_mode);
|
||||
|
||||
if self.theme.is_none() {
|
||||
self.style = program.style(&self.default_theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// mundy always notifies system theme changes, so we
|
||||
// just restore the default theme mode.
|
||||
let new_mode = if new_mode == theme::Mode::None {
|
||||
theme::Base::mode(&self.default_theme)
|
||||
} else {
|
||||
new_mode
|
||||
};
|
||||
|
||||
window.set_theme(conversion::window_theme(new_mode));
|
||||
}
|
||||
|
||||
self.theme_mode = new_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue