Merge branch 'master' into feature/test-recorder

This commit is contained in:
Héctor Ramón Jiménez 2025-09-11 04:57:17 +02:00
commit a052ce58b0
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
69 changed files with 1555 additions and 833 deletions

View file

@ -26,4 +26,3 @@ jobs:
cargo test --verbose --workspace cargo test --verbose --workspace
cargo test --verbose --workspace -- --ignored cargo test --verbose --workspace -- --ignored
cargo test --verbose --workspace --all-features cargo test --verbose --workspace --all-features
cargo test --verbose --workspace --all-features -- --ignored

789
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,7 @@ all-features = true
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[features] [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 # Enables the `wgpu` GPU-accelerated renderer backend
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
# Enables the `tiny-skia` software renderer backend # Enables the `tiny-skia` software renderer backend
@ -56,14 +56,14 @@ tokio = ["iced_futures/tokio"]
# Enables `smol` as the `executor::Default` on native platforms # Enables `smol` as the `executor::Default` on native platforms
smol = ["iced_futures/smol"] smol = ["iced_futures/smol"]
# Enables querying system information # Enables querying system information
system = ["iced_winit/system"] sysinfo = ["iced_winit/sysinfo"]
# Enables broken "sRGB linear" blending to reproduce color management of the Web # Enables broken "sRGB linear" blending to reproduce color management of the Web
web-colors = ["iced_renderer/web-colors"] web-colors = ["iced_renderer/web-colors"]
# Enables pixel snapping for crisp edges by default (can cause jitter!) # Enables pixel snapping for crisp edges by default (can cause jitter!)
crisp = ["iced_core/crisp", "iced_widget/crisp"] crisp = ["iced_core/crisp", "iced_widget/crisp"]
# Enables the WebGL backend # Enables the WebGL backend
webgl = ["iced_renderer/webgl"] webgl = ["iced_renderer/webgl"]
# Enables syntax highligthing # Enables syntax highlighting
highlighter = ["iced_highlighter", "iced_widget/highlighter"] highlighter = ["iced_highlighter", "iced_widget/highlighter"]
# Enables the `widget::selector` module # Enables the `widget::selector` module
selector = ["iced_runtime/selector"] selector = ["iced_runtime/selector"]
@ -71,14 +71,18 @@ selector = ["iced_runtime/selector"]
advanced = ["iced_core/advanced", "iced_widget/advanced"] advanced = ["iced_core/advanced", "iced_widget/advanced"]
# Embeds Fira Sans into the final application; useful for testing and Wasm builds # Embeds Fira Sans into the final application; useful for testing and Wasm builds
fira-sans = ["iced_renderer/fira-sans"] fira-sans = ["iced_renderer/fira-sans"]
# Auto-detects light/dark mode for the built-in theme # Enables basic text shaping by default
auto-detect-theme = ["iced_core/auto-detect-theme"] basic-shaping = ["iced_core/basic-shaping"]
# Enables advanced text shaping by default
advanced-shaping = ["iced_core/advanced-shaping"]
# Enables strict assertions for debugging purposes at the expense of performance # Enables strict assertions for debugging purposes at the expense of performance
strict-assertions = ["iced_renderer/strict-assertions"] strict-assertions = ["iced_renderer/strict-assertions"]
# Redraws on every runtime event, and not only when a widget requests it # Redraws on every runtime event, and not only when a widget requests it
unconditional-rendering = ["iced_winit/unconditional-rendering"] unconditional-rendering = ["iced_winit/unconditional-rendering"]
# Enables support for the `sipper` library # Enables support for the `sipper` library
sipper = ["iced_runtime/sipper"] sipper = ["iced_runtime/sipper"]
# Enables Linux system theme detection
linux-theme-detection = ["iced_winit/linux-theme-detection"]
[dependencies] [dependencies]
iced_debug.workspace = true iced_debug.workspace = true
@ -181,10 +185,9 @@ bytemuck = { version = "1.0", features = ["derive"] }
bytes = "1.6" bytes = "1.6"
cargo-hot = { package = "cargo-hot-protocol", git = "https://github.com/hecrj/cargo-hot.git", rev = "b8dc518b8640928178a501257e353b73bc06cf47" } cargo-hot = { package = "cargo-hot-protocol", git = "https://github.com/hecrj/cargo-hot.git", rev = "b8dc518b8640928178a501257e353b73bc06cf47" }
cosmic-text = "0.14" 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 } futures = { version = "0.3", default-features = false }
glam = "0.25" glam = "0.25"
cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "453cedec0d2ec563bd7fa87e84a2319bcebb1ba3" }
guillotiere = "0.6" guillotiere = "0.6"
half = "2.2" half = "2.2"
image = { version = "0.25", default-features = false } image = { version = "0.25", default-features = false }
@ -194,18 +197,19 @@ lilt = "0.8"
log = "0.4" log = "0.4"
lyon = "1.0" lyon = "1.0"
lyon_path = "1.0" lyon_path = "1.0"
mundy = { version = "0.2", default-features = false }
nom = "8" nom = "8"
num-traits = "0.2" num-traits = "0.2"
ouroboros = "0.18" ouroboros = "0.18"
png = "0.17" png = "0.18"
pulldown-cmark = "0.12" pulldown-cmark = "0.12"
qrcode = { version = "0.13", default-features = false } qrcode = { version = "0.13", default-features = false }
raw-window-handle = "0.6" raw-window-handle = "0.6"
resvg = "0.42" resvg = "0.42"
rfd = "0.15" rfd = "0.15"
rustc-hash = "2.0" rustc-hash = "2.0"
serde = "1.0"
semver = "1.0" semver = "1.0"
serde = "1.0"
sha2 = "0.10" sha2 = "0.10"
sipper = "0.1" sipper = "0.1"
smol = "2" smol = "2"

View file

@ -14,9 +14,10 @@ keywords.workspace = true
workspace = true workspace = true
[features] [features]
auto-detect-theme = ["dep:dark-light"]
advanced = [] advanced = []
crisp = [] crisp = []
basic-shaping = []
advanced-shaping = []
[dependencies] [dependencies]
bitflags.workspace = true bitflags.workspace = true
@ -30,9 +31,6 @@ smol_str.workspace = true
thiserror.workspace = true thiserror.workspace = true
web-time.workspace = true web-time.workspace = true
dark-light.workspace = true
dark-light.optional = true
serde.workspace = true serde.workspace = true
serde.optional = true serde.optional = true
serde.features = ["derive"] serde.features = ["derive"]

View file

@ -78,14 +78,19 @@ where
let total_spacing = spacing * items.len().saturating_sub(1) as f32; let total_spacing = spacing * items.len().saturating_sub(1) as f32;
let max_cross = axis.cross(limits.max()); let max_cross = axis.cross(limits.max());
let compression = limits.compression(); let (main_compress, cross_compress) = {
let (main_compress, cross_compress) = let compression = limits.compression();
axis.pack(compression.width, compression.height); axis.pack(compression.width, compression.height)
};
let compression = {
let (compress_x, compress_y) = axis.pack(main_compress, false);
Size::new(compress_x, compress_y)
};
let mut fill_main_sum = 0; let mut fill_main_sum = 0;
let mut some_fill_cross = false; let mut some_fill_cross = false;
let mut cross = if cross_compress { 0.0 } else { max_cross }; let mut cross = if cross_compress { 0.0 } else { max_cross };
let mut available = axis.main(limits.max()) - total_spacing; let mut available = axis.main(limits.max()) - total_spacing;
let mut nodes: Vec<Node> = Vec::with_capacity(items.len()); let mut nodes: Vec<Node> = Vec::with_capacity(items.len());

View file

@ -130,8 +130,17 @@ impl From<Alignment> for alignment::Horizontal {
} }
/// The shaping strategy of some text. /// The shaping strategy of some text.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Shaping { pub enum Shaping {
/// Auto-detect the best shaping strategy from the text.
///
/// This strategy will use [`Basic`](Self::Basic) shaping if the
/// text consists of only ASCII characters; otherwise, it will
/// use [`Advanced`](Self::Advanced) shaping.
///
/// This is the default, if neither the `basic-shaping` nor `advanced-shaping`
/// features are enabled.
Auto,
/// No shaping and no font fallback. /// No shaping and no font fallback.
/// ///
/// This shaping strategy is very cheap, but it will not display complex /// This shaping strategy is very cheap, but it will not display complex
@ -140,8 +149,8 @@ pub enum Shaping {
/// You should use this strategy when you have complete control of the text /// You should use this strategy when you have complete control of the text
/// and the font you are displaying in your application. /// and the font you are displaying in your application.
/// ///
/// This is the default. /// This will be the default if the `basic-shaping` feature is enabled and
#[default] /// the `advanced-shaping` feature is disabled.
Basic, Basic,
/// Advanced text shaping and font fallback. /// Advanced text shaping and font fallback.
/// ///
@ -150,9 +159,23 @@ pub enum Shaping {
/// may be needed to display all of the glyphs. /// may be needed to display all of the glyphs.
/// ///
/// Advanced shaping is expensive! You should only enable it when necessary. /// Advanced shaping is expensive! You should only enable it when necessary.
///
/// This will be the default if the `advanced-shaping` feature is enabled.
Advanced, Advanced,
} }
impl Default for Shaping {
fn default() -> Self {
if cfg!(feature = "advanced-shaping") {
Self::Advanced
} else if cfg!(feature = "basic-shaping") {
Self::Basic
} else {
Self::Auto
}
}
}
/// The wrapping strategy of some text. /// The wrapping strategy of some text.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Wrapping { pub enum Wrapping {

View file

@ -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 { impl fmt::Display for Theme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { 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. /// The base style of a theme.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style { pub struct Style {
@ -268,7 +255,13 @@ pub struct Style {
/// The default blank style of a theme. /// The default blank style of a theme.
pub trait Base { 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; fn base(&self) -> Style;
/// Returns the color [`Palette`] of the theme. /// Returns the color [`Palette`] of the theme.
@ -280,6 +273,39 @@ pub trait Base {
} }
impl Base for Theme { 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 { fn base(&self) -> Style {
default(self) default(self)
} }

View file

@ -15,7 +15,7 @@ pub struct Screenshot {
pub size: Size<u32>, pub size: Size<u32>,
/// The scale factor of the [`Screenshot`]. This can be useful when converting between widget /// The scale factor of the [`Screenshot`]. This can be useful when converting between widget
/// bounds (which are in logical pixels) to crop screenshots. /// bounds (which are in logical pixels) to crop screenshots.
pub scale_factor: f64, pub scale_factor: f32,
} }
impl Debug for Screenshot { impl Debug for Screenshot {
@ -35,7 +35,7 @@ impl Screenshot {
pub fn new( pub fn new(
bytes: impl Into<Bytes>, bytes: impl Into<Bytes>,
size: Size<u32>, size: Size<u32>,
scale_factor: f64, scale_factor: f32,
) -> Self { ) -> Self {
Self { Self {
bytes: bytes.into(), bytes: bytes.into(),

View file

@ -11,7 +11,7 @@ mod time_machine;
use crate::core::border; use crate::core::border;
use crate::core::keyboard; 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::time::seconds;
use crate::core::window; use crate::core::window;
use crate::core::{ use crate::core::{
@ -98,7 +98,11 @@ where
state.subscription(&self.program) 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) state.theme(&self.program, window)
} }
@ -106,7 +110,7 @@ where
state.style(&self.program, theme) state.style(&self.program, theme)
} }
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
state.scale_factor(&self.program, window) state.scale_factor(&self.program, window)
} }
} }
@ -161,11 +165,11 @@ where
show_notification: true, show_notification: true,
time_machine: TimeMachine::new(), time_machine: TimeMachine::new(),
}, },
task::blocking(|mut sender| { Task::batch([task::blocking(|mut sender| {
thread::sleep(seconds(2)); thread::sleep(seconds(2));
let _ = sender.try_send(()); let _ = sender.try_send(());
}) })
.map(|_| Message::HideNotification), .map(|_| Message::HideNotification)]),
) )
} }
@ -306,10 +310,7 @@ where
let state = self.state(); let state = self.state();
let view = { let view = {
let theme = program.theme(state, window); let view = program.view(state, window);
let view: Element<'_, _, Theme, _> =
themer(theme, program.view(state, window)).into();
if self.time_machine.is_rewinding() { if self.time_machine.is_rewinding() {
view.map(|_| Event::Discard) view.map(|_| Event::Discard)
@ -318,11 +319,13 @@ where
} }
}; };
let theme = program let theme = || {
.theme(state, window) program
.palette() .theme(state, window)
.map(|palette| Theme::custom("iced devtools", palette)) .as_ref()
.unwrap_or_default(); .and_then(theme::Base::palette)
.map(|palette| Theme::custom("iced devtools", palette))
};
let setup = if let Mode::Setup(setup) = &self.mode { let setup = if let Mode::Setup(setup) = &self.mode {
let stage: Element<'_, _, Theme, P::Renderer> = match setup { let stage: Element<'_, _, Theme, P::Renderer> = match setup {
@ -346,7 +349,7 @@ where
} else { } else {
None None
} }
.map(|mode| Element::from(mode).map(Event::Message)); .map(|setup| themer(theme(), Element::from(setup).map(Event::Message)));
let notification = self let notification = self
.show_notification .show_notification
@ -359,20 +362,20 @@ where
}) })
}); });
themer( stack![view]
theme, .height(Fill)
stack![view] .push_maybe(setup.map(opaque))
.height(Fill) .push_maybe(notification.map(|notification| {
.push_maybe(setup.map(opaque)) themer(
.push_maybe(notification.map(|notification| { theme(),
bottom_right(opaque( bottom_right(opaque(
container(notification) container(notification)
.padding(10) .padding(10)
.style(container::dark), .style(container::dark),
)) )),
})), )
) }))
.into() .into()
} }
pub fn subscription(&self, program: &P) -> Subscription<Event<P>> { pub fn subscription(&self, program: &P) -> Subscription<Event<P>> {
@ -394,7 +397,7 @@ where
Subscription::batch([subscription, hotkeys, commands]) Subscription::batch([subscription, hotkeys, commands])
} }
pub fn theme(&self, program: &P, window: window::Id) -> P::Theme { pub fn theme(&self, program: &P, window: window::Id) -> Option<P::Theme> {
program.theme(self.state(), window) program.theme(self.state(), window)
} }
@ -402,7 +405,7 @@ where
program.style(self.state(), theme) program.style(self.state(), theme)
} }
pub fn scale_factor(&self, program: &P, window: window::Id) -> f64 { pub fn scale_factor(&self, program: &P, window: window::Id) -> f32 {
program.scale_factor(self.state(), window) program.scale_factor(self.state(), window)
} }

View file

@ -10,7 +10,7 @@ use iced::{Element, Fill, Point, Rectangle, Renderer, Subscription, Theme};
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
iced::application(Arc::new, Arc::update, Arc::view) iced::application(Arc::new, Arc::update, Arc::view)
.subscription(Arc::subscription) .subscription(Arc::subscription)
.theme(|_| Theme::Dark) .theme(Theme::Dark)
.run() .run()
} }

View file

@ -4,7 +4,7 @@ use iced::{Element, Theme};
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
iced::application(Example::default, Example::update, Example::view) iced::application(Example::default, Example::update, Example::view)
.theme(|_| Theme::CatppuccinMocha) .theme(Theme::CatppuccinMocha)
.run() .run()
} }

View file

@ -128,26 +128,25 @@ impl Primitive {
} }
impl shader::Primitive for Primitive { impl shader::Primitive for Primitive {
fn prepare( type Renderer = Pipeline;
fn initialize(
&self, &self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
storage: &mut shader::Storage, ) -> Pipeline {
Pipeline::new(device, queue, format)
}
fn prepare(
&self,
pipeline: &mut Pipeline,
device: &wgpu::Device,
queue: &wgpu::Queue,
_bounds: &Rectangle, _bounds: &Rectangle,
viewport: &Viewport, viewport: &Viewport,
) { ) {
if !storage.has::<Pipeline>() {
storage.store(Pipeline::new(
device,
queue,
format,
viewport.physical_size(),
));
}
let pipeline = storage.get_mut::<Pipeline>().unwrap();
// Upload data to GPU // Upload data to GPU
pipeline.update( pipeline.update(
device, device,
@ -161,14 +160,11 @@ impl shader::Primitive for Primitive {
fn render( fn render(
&self, &self,
pipeline: &Pipeline,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
storage: &shader::Storage,
target: &wgpu::TextureView, target: &wgpu::TextureView,
clip_bounds: &Rectangle<u32>, clip_bounds: &Rectangle<u32>,
) { ) {
// At this point our pipeline should always be initialized
let pipeline = storage.get::<Pipeline>().unwrap();
// Render primitive // Render primitive
pipeline.render( pipeline.render(
target, target,

View file

@ -32,7 +32,6 @@ impl Pipeline {
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
target_size: Size<u32>,
) -> Self { ) -> Self {
//vertices of one cube //vertices of one cube
let vertices = let vertices =
@ -62,8 +61,8 @@ impl Pipeline {
let depth_texture = device.create_texture(&wgpu::TextureDescriptor { let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("cubes depth texture"), label: Some("cubes depth texture"),
size: wgpu::Extent3d { size: wgpu::Extent3d {
width: target_size.width, width: 1,
height: target_size.height, height: 1,
depth_or_array_layers: 1, depth_or_array_layers: 1,
}, },
mip_level_count: 1, mip_level_count: 1,
@ -297,7 +296,7 @@ impl Pipeline {
uniforms, uniforms,
uniform_bind_group, uniform_bind_group,
vertices, vertices,
depth_texture_size: target_size, depth_texture_size: Size::new(1, 1),
depth_view, depth_view,
depth_pipeline, depth_pipeline,
} }

View file

@ -326,5 +326,8 @@ fn open_icon<'a, Message>() -> Element<'a, Message> {
fn icon<'a, Message>(codepoint: char) -> Element<'a, Message> { fn icon<'a, Message>(codepoint: char) -> Element<'a, Message> {
const ICON_FONT: Font = Font::with_name("editor-icons"); const ICON_FONT: Font = Font::with_name("editor-icons");
text(codepoint).font(ICON_FONT).into() text(codepoint)
.font(ICON_FONT)
.shaping(text::Shaping::Basic)
.into()
} }

View file

@ -37,7 +37,7 @@ impl Events {
} }
Message::EventOccurred(event) => { Message::EventOccurred(event) => {
if let Event::Window(window::Event::CloseRequested) = event { if let Event::Window(window::Event::CloseRequested) = event {
window::get_latest().and_then(window::close) window::latest().and_then(window::close)
} else { } else {
Task::none() Task::none()
} }
@ -47,7 +47,7 @@ impl Events {
Task::none() Task::none()
} }
Message::Exit => window::get_latest().and_then(window::close), Message::Exit => window::latest().and_then(window::close),
} }
} }

View file

@ -20,7 +20,7 @@ enum Message {
impl Exit { impl Exit {
fn update(&mut self, message: Message) -> Task<Message> { fn update(&mut self, message: Message) -> Task<Message> {
match message { match message {
Message::Confirm => window::get_latest().and_then(window::close), Message::Confirm => window::latest().and_then(window::close),
Message::Exit => { Message::Exit => {
self.show_confirm = true; self.show_confirm = true;

View file

@ -11,7 +11,7 @@ use iced::{
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
iced::application(Image::default, Image::update, Image::view) iced::application(Image::default, Image::update, Image::view)
.subscription(Image::subscription) .subscription(Image::subscription)
.theme(|_| Theme::TokyoNight) .theme(Theme::TokyoNight)
.run() .run()
} }

View file

@ -16,7 +16,7 @@ pub fn main() -> iced::Result {
iced::application(GameOfLife::default, GameOfLife::update, GameOfLife::view) iced::application(GameOfLife::default, GameOfLife::update, GameOfLife::view)
.subscription(GameOfLife::subscription) .subscription(GameOfLife::subscription)
.theme(|_| Theme::Dark) .theme(Theme::Dark)
.centered() .centered()
.run() .run()
} }

View file

@ -67,7 +67,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
let physical_size = window.inner_size(); let physical_size = window.inner_size();
let viewport = Viewport::with_physical_size( let viewport = Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height), Size::new(physical_size.width, physical_size.height),
window.scale_factor(), window.scale_factor() as f32,
); );
let clipboard = Clipboard::connect(window.clone()); let clipboard = Clipboard::connect(window.clone());
@ -212,7 +212,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
*viewport = Viewport::with_physical_size( *viewport = Viewport::with_physical_size(
Size::new(size.width, size.height), Size::new(size.width, size.height),
window.scale_factor(), window.scale_factor() as f32,
); );
surface.configure( surface.configure(
@ -345,7 +345,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
// Map window event to iced event // Map window event to iced event
if let Some(event) = conversion::window_event( if let Some(event) = conversion::window_event(
event, event,
window.scale_factor(), window.scale_factor() as f32,
*modifiers, *modifiers,
) { ) {
events.push(event); events.push(event);

View file

@ -19,11 +19,11 @@ pub fn main() -> iced::Result {
.run() .run()
} }
#[derive(Default, Debug)] #[derive(Debug, Default)]
struct Layout { struct Layout {
example: Example, example: Example,
explain: bool, explain: bool,
theme: Theme, theme: Option<Theme>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -51,7 +51,7 @@ impl Layout {
self.explain = explain; self.explain = explain;
} }
Message::ThemeSelected(theme) => { Message::ThemeSelected(theme) => {
self.theme = theme; self.theme = Some(theme);
} }
} }
} }
@ -74,7 +74,8 @@ impl Layout {
horizontal_space(), horizontal_space(),
checkbox("Explain", self.explain) checkbox("Explain", self.explain)
.on_toggle(Message::ExplainToggled), .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) .spacing(20)
.align_y(Center); .align_y(Center);
@ -94,14 +95,14 @@ impl Layout {
let controls = row([ let controls = row([
(!self.example.is_first()).then_some( (!self.example.is_first()).then_some(
button(text("← Previous").shaping(text::Shaping::Advanced)) button(text("← Previous"))
.padding([5, 10]) .padding([5, 10])
.on_press(Message::Previous) .on_press(Message::Previous)
.into(), .into(),
), ),
Some(horizontal_space().into()), Some(horizontal_space().into()),
(!self.example.is_last()).then_some( (!self.example.is_last()).then_some(
button(text("Next →").shaping(text::Shaping::Advanced)) button(text("Next →"))
.padding([5, 10]) .padding([5, 10])
.on_press(Message::Next) .on_press(Message::Next)
.into(), .into(),
@ -116,7 +117,7 @@ impl Layout {
.into() .into()
} }
fn theme(&self) -> Theme { fn theme(&self) -> Option<Theme> {
self.theme.clone() self.theme.clone()
} }
} }
@ -313,7 +314,7 @@ fn quotes<'a>() -> Element<'a, Message> {
"This is another reply", "This is another reply",
), ),
horizontal_rule(1), horizontal_rule(1),
text("A separator ↑").shaping(text::Shaping::Advanced), text("A separator ↑"),
] ]
.width(Shrink) .width(Shrink)
.spacing(10) .spacing(10)

View file

@ -26,7 +26,7 @@ struct Example {
struct Window { struct Window {
title: String, title: String,
scale_input: String, scale_input: String,
current_scale: f64, current_scale: f32,
theme: Theme, theme: Theme,
} }
@ -66,7 +66,7 @@ impl Example {
return Task::none(); return Task::none();
}; };
window::get_position(*last_window) window::position(*last_window)
.then(|last_position| { .then(|last_position| {
let position = last_position.map_or( let position = last_position.map_or(
window::Position::Default, window::Position::Default,
@ -113,7 +113,7 @@ impl Example {
Message::ScaleChanged(id, scale) => { Message::ScaleChanged(id, scale) => {
if let Some(window) = self.windows.get_mut(&id) { if let Some(window) = self.windows.get_mut(&id) {
window.current_scale = scale window.current_scale = scale
.parse::<f64>() .parse()
.unwrap_or(window.current_scale) .unwrap_or(window.current_scale)
.clamp(0.5, 5.0); .clamp(0.5, 5.0);
} }
@ -138,15 +138,11 @@ impl Example {
} }
} }
fn theme(&self, window: window::Id) -> Theme { fn theme(&self, window: window::Id) -> Option<Theme> {
if let Some(window) = self.windows.get(&window) { Some(self.windows.get(&window)?.theme.clone())
window.theme.clone()
} else {
Theme::default()
}
} }
fn scale_factor(&self, window: window::Id) -> f64 { fn scale_factor(&self, window: window::Id) -> f32 {
self.windows self.windows
.get(&window) .get(&window)
.map(|window| window.current_scale) .map(|window| window.current_scale)

View file

@ -20,7 +20,7 @@ struct QRGenerator {
data: String, data: String,
qr_code: Option<qr_code::Data>, qr_code: Option<qr_code::Data>,
total_size: Option<f32>, total_size: Option<f32>,
theme: Theme, theme: Option<Theme>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -58,7 +58,7 @@ impl QRGenerator {
self.total_size = Some(total_size); self.total_size = Some(total_size);
} }
Message::ThemeChanged(theme) => { Message::ThemeChanged(theme) => {
self.theme = theme; self.theme = Some(theme);
} }
} }
} }
@ -78,7 +78,8 @@ impl QRGenerator {
let choose_theme = row![ let choose_theme = row![
text("Theme:"), 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) .spacing(10)
.align_y(Center); .align_y(Center);
@ -107,7 +108,7 @@ impl QRGenerator {
center(content).padding(20).into() center(content).padding(20).into()
} }
fn theme(&self) -> Theme { fn theme(&self) -> Option<Theme> {
self.theme.clone() self.theme.clone()
} }
} }

View file

@ -49,7 +49,7 @@ impl Example {
fn update(&mut self, message: Message) -> Task<Message> { fn update(&mut self, message: Message) -> Task<Message> {
match message { match message {
Message::Screenshot => { Message::Screenshot => {
return window::get_latest() return window::latest()
.and_then(window::screenshot) .and_then(window::screenshot)
.map(Message::Screenshotted); .map(Message::Screenshotted);
} }

View file

@ -1 +1 @@
0650eb2c27c21c5d48e1e00031a52d8471d8a3b4e827ad502c4628914f5c1c13 129523830df064908cfa911214ba61dadc31d8975425ba3efaae69da9514a3d0

View file

@ -15,7 +15,7 @@ pub fn main() -> iced::Result {
#[derive(Default)] #[derive(Default)]
struct Styling { struct Styling {
theme: Theme, theme: Option<Theme>,
input_value: String, input_value: String,
slider_value: f32, slider_value: f32,
checkbox_value: bool, checkbox_value: bool,
@ -32,13 +32,14 @@ enum Message {
TogglerToggled(bool), TogglerToggled(bool),
PreviousTheme, PreviousTheme,
NextTheme, NextTheme,
ClearTheme,
} }
impl Styling { impl Styling {
fn update(&mut self, message: Message) { fn update(&mut self, message: Message) {
match message { match message {
Message::ThemeChanged(theme) => { Message::ThemeChanged(theme) => {
self.theme = theme; self.theme = Some(theme);
} }
Message::InputChanged(value) => self.input_value = value, Message::InputChanged(value) => self.input_value = value,
Message::ButtonPressed => {} Message::ButtonPressed => {}
@ -46,21 +47,29 @@ impl Styling {
Message::CheckboxToggled(value) => self.checkbox_value = value, Message::CheckboxToggled(value) => self.checkbox_value = value,
Message::TogglerToggled(value) => self.toggler_value = value, Message::TogglerToggled(value) => self.toggler_value = value,
Message::PreviousTheme | Message::NextTheme => { Message::PreviousTheme | Message::NextTheme => {
if let Some(current) = Theme::ALL let current = Theme::ALL.iter().position(|candidate| {
.iter() self.theme.as_ref() == Some(candidate)
.position(|candidate| &self.theme == candidate) });
{
self.theme = if matches!(message, Message::NextTheme) { self.theme = Some(if matches!(message, Message::NextTheme) {
Theme::ALL[(current + 1) % Theme::ALL.len()].clone() Theme::ALL[current.map(|current| current + 1).unwrap_or(0)
} else if current == 0 { % Theme::ALL.len()]
.clone()
} else {
let current = current.unwrap_or(0);
if current == 0 {
Theme::ALL Theme::ALL
.last() .last()
.expect("Theme::ALL must not be empty") .expect("Theme::ALL must not be empty")
.clone() .clone()
} else { } else {
Theme::ALL[current - 1].clone() Theme::ALL[current - 1].clone()
}; }
} });
}
Message::ClearTheme => {
self.theme = None;
} }
} }
} }
@ -68,8 +77,9 @@ impl Styling {
fn view(&self) -> Element<'_, Message> { fn view(&self) -> Element<'_, Message> {
let choose_theme = column![ let choose_theme = column![
text("Theme:"), text("Theme:"),
pick_list(Theme::ALL, Some(&self.theme), Message::ThemeChanged) pick_list(Theme::ALL, self.theme.as_ref(), Message::ThemeChanged)
.width(Fill), .width(Fill)
.placeholder("System"),
] ]
.spacing(10); .spacing(10);
@ -186,11 +196,14 @@ impl Styling {
keyboard::key::Named::ArrowDown keyboard::key::Named::ArrowDown
| keyboard::key::Named::ArrowRight, | keyboard::key::Named::ArrowRight,
) => Some(Message::NextTheme), ) => Some(Message::NextTheme),
keyboard::Key::Named(keyboard::key::Named::Space) => {
Some(Message::ClearTheme)
}
_ => None, _ => None,
}) })
} }
fn theme(&self) -> Theme { fn theme(&self) -> Option<Theme> {
self.theme.clone() self.theme.clone()
} }
} }
@ -210,9 +223,7 @@ mod tests {
.cloned() .cloned()
.map(|theme| { .map(|theme| {
let mut styling = Styling::default(); let mut styling = Styling::default();
styling.update(Message::ThemeChanged(theme)); styling.update(Message::ThemeChanged(theme.clone()));
let theme = styling.theme();
let mut ui = simulator(styling.view()); let mut ui = simulator(styling.view());
let snapshot = ui.snapshot(&theme)?; let snapshot = ui.snapshot(&theme)?;

View file

@ -7,6 +7,6 @@ publish = false
[dependencies] [dependencies]
iced.workspace = true iced.workspace = true
iced.features = ["system"] iced.features = ["sysinfo"]
bytesize = "1.1" bytesize = "1.1"

View file

@ -1,5 +1,6 @@
use iced::system;
use iced::widget::{button, center, column, text}; use iced::widget::{button, center, column, text};
use iced::{Element, Task, system}; use iced::{Element, Task};
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
iced::application(Example::new, Example::update, Example::view).run() iced::application(Example::new, Example::update, Example::view).run()
@ -26,7 +27,7 @@ impl Example {
fn new() -> (Self, Task<Message>) { fn new() -> (Self, Task<Message>) {
( (
Self::Loading, Self::Loading,
system::fetch_information().map(Message::InformationReceived), system::information().map(Message::InformationReceived),
) )
} }

View file

@ -8,7 +8,7 @@ use iced::{Center, Element, Fill, Font, Right, Theme};
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
iced::application(Table::new, Table::update, Table::view) iced::application(Table::new, Table::update, Table::view)
.theme(|_| Theme::CatppuccinMocha) .theme(Theme::CatppuccinMocha)
.run() .run()
} }

View file

@ -1 +1 @@
99f418007af163f172e163565f166da31015521e1bf7de95fa55cda2fb5a7db5 0acb67235c6a11014a2d2b825e0a70069bca0c67bee0cdb38a0144fc72b25220

View file

@ -6,7 +6,7 @@ use iced::widget::{
use iced::window; use iced::window;
use iced::{ use iced::{
Application, Center, Element, Fill, Font, Function, Preset, Program, Application, Center, Element, Fill, Font, Function, Preset, Program,
Subscription, Task as Command, Subscription, Task as Command, Theme,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -156,7 +156,7 @@ impl Todos {
operation::focus_next() operation::focus_next()
} }
} }
Message::ToggleFullscreen(mode) => window::get_latest() Message::ToggleFullscreen(mode) => window::latest()
.and_then(move |window| window::set_mode(window, mode)), .and_then(move |window| window::set_mode(window, mode)),
Message::Loaded(_) => Command::none(), Message::Loaded(_) => Command::none(),
}; };
@ -199,7 +199,7 @@ impl Todos {
let title = text("todos") let title = text("todos")
.width(Fill) .width(Fill)
.size(100) .size(100)
.color([0.5, 0.5, 0.5]) .style(subtle)
.align_x(Center); .align_x(Center);
let input = text_input("What needs to be done?", input_value) let input = text_input("What needs to be done?", input_value)
@ -452,7 +452,7 @@ fn empty_message(message: &str) -> Element<'_, Message> {
.width(Fill) .width(Fill)
.size(25) .size(25)
.align_x(Center) .align_x(Center)
.color([0.7, 0.7, 0.7]), .style(subtle),
) )
.height(200) .height(200)
.into() .into()
@ -465,6 +465,7 @@ fn icon(unicode: char) -> Text<'static> {
.font(Font::with_name("Iced-Todos-Icons")) .font(Font::with_name("Iced-Todos-Icons"))
.width(20) .width(20)
.align_x(Center) .align_x(Center)
.shaping(text::Shaping::Basic)
} }
fn edit_icon() -> Text<'static> { fn edit_icon() -> Text<'static> {
@ -475,6 +476,12 @@ fn delete_icon() -> Text<'static> {
icon('\u{F1F8}') icon('\u{F1F8}')
} }
fn subtle(theme: &Theme) -> text::Style {
text::Style {
color: Some(theme.extended_palette().background.strongest.color),
}
}
// Persistence // Persistence
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
struct SavedState { struct SavedState {
@ -625,6 +632,7 @@ mod tests {
} }
#[test] #[test]
#[ignore]
fn it_creates_a_new_task() -> Result<(), Error> { fn it_creates_a_new_task() -> Result<(), Error> {
let (mut todos, _command) = Todos::new(); let (mut todos, _command) = Todos::new();
let _command = todos.update(Message::Loaded(Err(LoadError::File))); let _command = todos.update(Message::Loaded(Err(LoadError::File)));

View file

@ -1,11 +1,10 @@
use iced::border;
use iced::widget::{Button, Column, Container, Slider}; use iced::widget::{Button, Column, Container, Slider};
use iced::widget::{ use iced::widget::{
button, center_x, center_y, checkbox, column, horizontal_space, image, button, center_x, center_y, checkbox, column, horizontal_space, image,
radio, rich_text, row, scrollable, slider, span, text, text_input, toggler, radio, rich_text, row, scrollable, slider, span, text, text_input, toggler,
vertical_space, 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 { pub fn main() -> iced::Result {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -201,7 +200,7 @@ impl Tour {
Self::container("Welcome!") Self::container("Welcome!")
.push( .push(
"This is a simple tour meant to showcase a bunch of \ "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( .push(
"Iced is a cross-platform GUI library for Rust focused on \ "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, \ built on top of wgpu, a graphics library supporting Vulkan, \
Metal, DX11, and DX12.", Metal, DX11, and DX12.",
) )
.push({ .push(
let theme = Theme::default();
let palette = theme.extended_palette();
rich_text![ rich_text![
"Additionally, this tour can also run on WebAssembly ", "Additionally, this tour can also run on WebAssembly ",
"by leveraging ", "by leveraging ",
span("trunk") span("trunk")
.color(palette.primary.base.color) .color(color!(0x7777FF))
.background(palette.background.weakest.color) .underline(true)
.border(
border::rounded(2)
.width(1)
.color(palette.background.weak.color)
)
.padding([0, 2])
.font(Font::MONOSPACE) .font(Font::MONOSPACE)
.link(Message::OpenTrunk), .link(Message::OpenTrunk),
"." "."
] ]
.on_link_click(std::convert::identity) .on_link_click(std::convert::identity),
}) )
.push( .push(
"You will need to interact with the UI in order to reach \ "You will need to interact with the UI in order to reach \
the end!", the end!",

View file

@ -11,7 +11,7 @@ pub fn main() -> iced::Result {
VectorialText::update, VectorialText::update,
VectorialText::view, VectorialText::view,
) )
.theme(|_| Theme::Dark) .theme(Theme::Dark)
.run() .run()
} }

View file

@ -13,7 +13,7 @@ use iced::{
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
iced::application(Example::default, Example::update, Example::view) iced::application(Example::default, Example::update, Example::view)
.subscription(Example::subscription) .subscription(Example::subscription)
.theme(|_| Theme::Dark) .theme(Theme::Dark)
.run() .run()
} }

View file

@ -37,6 +37,7 @@ where
event: Event::Window(window::Event::RedrawRequested(_)), event: Event::Window(window::Event::RedrawRequested(_)),
.. ..
} }
| subscription::Event::SystemThemeChanged(_)
| subscription::Event::PlatformSpecific(_) => None, | subscription::Event::PlatformSpecific(_) => None,
subscription::Event::Interaction { subscription::Event::Interaction {
window, window,
@ -66,7 +67,8 @@ where
event, event,
status, status,
} => f(event, status, window), } => f(event, status, window),
subscription::Event::PlatformSpecific(_) => None, subscription::Event::SystemThemeChanged(_)
| subscription::Event::PlatformSpecific(_) => None,
}) })
} }

View file

@ -4,6 +4,7 @@ mod tracker;
pub use tracker::Tracker; pub use tracker::Tracker;
use crate::core::event; use crate::core::event;
use crate::core::theme;
use crate::core::window; use crate::core::window;
use crate::futures::Stream; use crate::futures::Stream;
use crate::{BoxStream, MaybeSend}; use crate::{BoxStream, MaybeSend};
@ -27,6 +28,9 @@ pub enum Event {
status: event::Status, status: event::Status,
}, },
/// The system theme has changed.
SystemThemeChanged(theme::Mode),
/// A platform specific event. /// A platform specific event.
PlatformSpecific(PlatformSpecific), 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 where
I: Hash + 'static, I: Hash + 'static,
F: Fn(Event) -> Option<T> + MaybeSend + 'static, F: Fn(Event) -> Option<T> + MaybeSend + 'static,

View file

@ -59,7 +59,7 @@ pub trait Compositor: Sized {
); );
/// Returns [`Information`] used by this [`Compositor`]. /// Returns [`Information`] used by this [`Compositor`].
fn fetch_information(&self) -> Information; fn information(&self) -> Information;
/// Loads a font from its bytes. /// Loads a font from its bytes.
fn load_font(&mut self, font: Cow<'static, [u8]>) { 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 load_font(&mut self, _font: Cow<'static, [u8]>) {}
fn fetch_information(&self) -> Information { fn information(&self) -> Information {
Information { Information {
adapter: String::from("Null Renderer"), adapter: String::from("Null Renderer"),
backend: String::from("Null"), backend: String::from("Null"),

View file

@ -176,7 +176,7 @@ impl Default for Text {
font: Font::default(), font: Font::default(),
align_x: Alignment::Default, align_x: Alignment::Default,
align_y: alignment::Vertical::Top, align_y: alignment::Vertical::Top,
shaping: Shaping::Basic, shaping: Shaping::default(),
} }
} }
} }

View file

@ -323,8 +323,15 @@ fn to_align(alignment: Alignment) -> Option<cosmic_text::Align> {
} }
/// Converts some [`Shaping`] strategy to a [`cosmic_text::Shaping`] strategy. /// Converts some [`Shaping`] strategy to a [`cosmic_text::Shaping`] strategy.
pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { pub fn to_shaping(shaping: Shaping, text: &str) -> cosmic_text::Shaping {
match shaping { match shaping {
Shaping::Auto => {
if text.is_ascii() {
cosmic_text::Shaping::Basic
} else {
cosmic_text::Shaping::Advanced
}
}
Shaping::Basic => cosmic_text::Shaping::Basic, Shaping::Basic => cosmic_text::Shaping::Basic,
Shaping::Advanced => cosmic_text::Shaping::Advanced, Shaping::Advanced => cosmic_text::Shaping::Advanced,
} }

View file

@ -55,7 +55,7 @@ impl Cache {
font_system, font_system,
key.content, key.content,
&text::to_attributes(key.font), &text::to_attributes(key.font),
text::to_shaping(key.shaping), text::to_shaping(key.shaping, key.content),
); );
let bounds = text::align(&mut buffer, font_system, key.align_x); let bounds = text::align(&mut buffer, font_system, key.align_x);

View file

@ -88,7 +88,7 @@ impl core::text::Paragraph for Paragraph {
font_system.raw(), font_system.raw(),
text.content, text.content,
&text::to_attributes(text.font), &text::to_attributes(text.font),
text::to_shaping(text.shaping), text::to_shaping(text.shaping, text.content),
); );
let min_bounds = let min_bounds =
@ -158,7 +158,7 @@ impl core::text::Paragraph for Paragraph {
(span.text.as_ref(), attrs.metadata(i)) (span.text.as_ref(), attrs.metadata(i))
}), }),
&text::to_attributes(text.font), &text::to_attributes(text.font),
text::to_shaping(text.shaping), cosmic_text::Shaping::Advanced,
None, None,
); );

View file

@ -5,19 +5,19 @@ use crate::core::{Size, Transformation};
pub struct Viewport { pub struct Viewport {
physical_size: Size<u32>, physical_size: Size<u32>,
logical_size: Size<f32>, logical_size: Size<f32>,
scale_factor: f64, scale_factor: f32,
projection: Transformation, projection: Transformation,
} }
impl Viewport { impl Viewport {
/// Creates a new [`Viewport`] with the given physical dimensions and scale /// Creates a new [`Viewport`] with the given physical dimensions and scale
/// factor. /// factor.
pub fn with_physical_size(size: Size<u32>, scale_factor: f64) -> Viewport { pub fn with_physical_size(size: Size<u32>, scale_factor: f32) -> Viewport {
Viewport { Viewport {
physical_size: size, physical_size: size,
logical_size: Size::new( logical_size: Size::new(
(size.width as f64 / scale_factor) as f32, size.width as f32 / scale_factor,
(size.height as f64 / scale_factor) as f32, size.height as f32 / scale_factor,
), ),
scale_factor, scale_factor,
projection: Transformation::orthographic(size.width, size.height), projection: Transformation::orthographic(size.width, size.height),
@ -45,7 +45,7 @@ impl Viewport {
} }
/// Returns the scale factor of the [`Viewport`]. /// Returns the scale factor of the [`Viewport`].
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f32 {
self.scale_factor self.scale_factor
} }

View file

@ -32,7 +32,7 @@ pub trait Program: Sized {
type Message: Send + 'static; type Message: Send + 'static;
/// The theme of the program. /// The theme of the program.
type Theme: Default + theme::Base; type Theme: theme::Base;
/// The renderer of the program. /// The renderer of the program.
type Renderer: Renderer; type Renderer: Renderer;
@ -97,15 +97,19 @@ pub trait Program: Sized {
Subscription::none() Subscription::none()
} }
fn theme(&self, _state: &Self::State, _window: window::Id) -> Self::Theme { fn theme(
<Self::Theme as Default>::default() &self,
_state: &Self::State,
_window: window::Id,
) -> Option<Self::Theme> {
None
} }
fn style(&self, _state: &Self::State, theme: &Self::Theme) -> theme::Style { fn style(&self, _state: &Self::State, theme: &Self::Theme) -> theme::Style {
theme::Base::base(theme) theme::Base::base(theme)
} }
fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f64 { fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f32 {
1.0 1.0
} }
@ -175,7 +179,7 @@ pub fn with_title<P: Program>(
&self, &self,
state: &Self::State, state: &Self::State,
window: window::Id, window: window::Id,
) -> Self::Theme { ) -> Option<Self::Theme> {
self.program.theme(state, window) self.program.theme(state, window)
} }
@ -194,7 +198,7 @@ pub fn with_title<P: Program>(
self.program.style(state, theme) self.program.style(state, theme)
} }
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
self.program.scale_factor(state, window) self.program.scale_factor(state, window)
} }
} }
@ -269,7 +273,7 @@ pub fn with_subscription<P: Program>(
&self, &self,
state: &Self::State, state: &Self::State,
window: window::Id, window: window::Id,
) -> Self::Theme { ) -> Option<Self::Theme> {
self.program.theme(state, window) self.program.theme(state, window)
} }
@ -281,7 +285,7 @@ pub fn with_subscription<P: Program>(
self.program.style(state, theme) self.program.style(state, theme)
} }
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
self.program.scale_factor(state, window) self.program.scale_factor(state, window)
} }
} }
@ -295,7 +299,7 @@ pub fn with_subscription<P: Program>(
/// Decorates a [`Program`] with the given theme function. /// Decorates a [`Program`] with the given theme function.
pub fn with_theme<P: Program>( pub fn with_theme<P: Program>(
program: P, 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> { ) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> {
struct WithTheme<P, F> { struct WithTheme<P, F> {
program: P, program: P,
@ -304,7 +308,7 @@ pub fn with_theme<P: Program>(
impl<P: Program, F> Program for WithTheme<P, F> impl<P: Program, F> Program for WithTheme<P, F>
where where
F: Fn(&P::State, window::Id) -> P::Theme, F: Fn(&P::State, window::Id) -> Option<P::Theme>,
{ {
type State = P::State; type State = P::State;
type Message = P::Message; type Message = P::Message;
@ -316,7 +320,7 @@ pub fn with_theme<P: Program>(
&self, &self,
state: &Self::State, state: &Self::State,
window: window::Id, window: window::Id,
) -> Self::Theme { ) -> Option<Self::Theme> {
(self.theme)(state, window) (self.theme)(state, window)
} }
@ -371,7 +375,7 @@ pub fn with_theme<P: Program>(
self.program.style(state, theme) self.program.style(state, theme)
} }
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
self.program.scale_factor(state, window) self.program.scale_factor(state, window)
} }
} }
@ -454,11 +458,11 @@ pub fn with_style<P: Program>(
&self, &self,
state: &Self::State, state: &Self::State,
window: window::Id, window: window::Id,
) -> Self::Theme { ) -> Option<Self::Theme> {
self.program.theme(state, window) self.program.theme(state, window)
} }
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
self.program.scale_factor(state, window) self.program.scale_factor(state, window)
} }
} }
@ -469,7 +473,7 @@ pub fn with_style<P: Program>(
/// Decorates a [`Program`] with the given scale factor function. /// Decorates a [`Program`] with the given scale factor function.
pub fn with_scale_factor<P: Program>( pub fn with_scale_factor<P: Program>(
program: P, program: P,
f: impl Fn(&P::State, window::Id) -> f64, f: impl Fn(&P::State, window::Id) -> f32,
) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { ) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> {
struct WithScaleFactor<P, F> { struct WithScaleFactor<P, F> {
program: P, program: P,
@ -478,7 +482,7 @@ pub fn with_scale_factor<P: Program>(
impl<P: Program, F> Program for WithScaleFactor<P, F> impl<P: Program, F> Program for WithScaleFactor<P, F>
where where
F: Fn(&P::State, window::Id) -> f64, F: Fn(&P::State, window::Id) -> f32,
{ {
type State = P::State; type State = P::State;
type Message = P::Message; type Message = P::Message;
@ -533,7 +537,7 @@ pub fn with_scale_factor<P: Program>(
&self, &self,
state: &Self::State, state: &Self::State,
window: window::Id, window: window::Id,
) -> Self::Theme { ) -> Option<Self::Theme> {
self.program.theme(state, window) self.program.theme(state, window)
} }
@ -545,7 +549,7 @@ pub fn with_scale_factor<P: Program>(
self.program.style(state, theme) self.program.style(state, theme)
} }
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
(self.scale_factor)(state, window) (self.scale_factor)(state, window)
} }
} }
@ -624,7 +628,7 @@ pub fn with_executor<P: Program, E: Executor>(
&self, &self,
state: &Self::State, state: &Self::State,
window: window::Id, window: window::Id,
) -> Self::Theme { ) -> Option<Self::Theme> {
self.program.theme(state, window) self.program.theme(state, window)
} }
@ -636,7 +640,7 @@ pub fn with_executor<P: Program, E: Executor>(
self.program.style(state, theme) self.program.style(state, theme)
} }
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
self.program.scale_factor(state, window) self.program.scale_factor(state, window)
} }
} }
@ -697,7 +701,7 @@ impl<P: Program> Instance<P> {
} }
/// Returns the current theme of the [`Instance`]. /// 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) self.program.theme(&self.state, window)
} }
@ -707,7 +711,7 @@ impl<P: Program> Instance<P> {
} }
/// Returns the current scale factor of the [`Instance`]. /// Returns the current scale factor of the [`Instance`].
pub fn scale_factor(&self, window: window::Id) -> f64 { pub fn scale_factor(&self, window: window::Id) -> f32 {
self.program.scale_factor(&self.state, window) self.program.scale_factor(&self.state, window)
} }
} }

View file

@ -312,8 +312,8 @@ where
delegate!(self, compositor, compositor.load_font(font)); delegate!(self, compositor, compositor.load_font(font));
} }
fn fetch_information(&self) -> compositor::Information { fn information(&self) -> compositor::Information {
delegate!(self, compositor, compositor.fetch_information()) delegate!(self, compositor, compositor.information())
} }
fn present( fn present(

View file

@ -1,11 +1,20 @@
//! Access the native system. //! Access the native system.
use crate::core::theme;
use crate::futures::futures::channel::oneshot; 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. /// An operation to be performed on the system.
#[derive(Debug)] #[derive(Debug)]
pub enum Action { pub enum Action {
/// Query system information and produce `T` with the result. /// Send available system information.
QueryInformation(oneshot::Sender<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). /// 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 /// Model information for the active graphics adapter
pub graphics_adapter: String, 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)
})
}

View file

@ -266,12 +266,12 @@ pub fn close<T>(id: Id) -> Task<T> {
} }
/// Gets the window [`Id`] of the oldest window. /// 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))) task::oneshot(|channel| crate::Action::Window(Action::GetOldest(channel)))
} }
/// Gets the window [`Id`] of the latest window. /// 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))) 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. /// 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| { task::oneshot(move |channel| {
crate::Action::Window(Action::GetSize(id, channel)) crate::Action::Window(Action::GetSize(id, channel))
}) })
} }
/// Gets the maximized state of the window with the given [`Id`]. /// 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| { task::oneshot(move |channel| {
crate::Action::Window(Action::GetMaximized(id, 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`]. /// 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| { task::oneshot(move |channel| {
crate::Action::Window(Action::GetMinimized(id, 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`]. /// 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| { task::oneshot(move |channel| {
crate::Action::Window(Action::GetPosition(id, channel)) crate::Action::Window(Action::GetPosition(id, channel))
}) })
} }
/// Gets the scale factor of the window with the given [`Id`]. /// 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| { task::oneshot(move |channel| {
crate::Action::Window(Action::GetScaleFactor(id, 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. /// 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| { task::oneshot(move |channel| {
crate::Action::Window(Action::GetMode(id, 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 /// Gets an identifier unique to the window, provided by the underlying windowing system. This is
/// not to be confused with [`Id`]. /// 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| { task::oneshot(|channel| {
crate::Action::Window(Action::GetRawId(id, channel)) crate::Action::Window(Action::GetRawId(id, channel))
}) })

View file

@ -7,7 +7,7 @@
//! //!
//! pub fn main() -> iced::Result { //! pub fn main() -> iced::Result {
//! iced::application(u64::default, update, view) //! iced::application(u64::default, update, view)
//! .theme(|_| Theme::Dark) //! .theme(Theme::Dark)
//! .centered() //! .centered()
//! .run() //! .run()
//! } //! }
@ -36,7 +36,8 @@ use crate::shell;
use crate::theme; use crate::theme;
use crate::window; use crate::window;
use crate::{ use crate::{
Element, Executor, Font, Preset, Result, Settings, Size, Subscription, Task, Element, Executor, Font, Preset, Result, Settings, Size, Subscription,
Task, Theme,
}; };
use iced_debug as debug; use iced_debug as debug;
@ -76,14 +77,14 @@ pub use timed::timed;
/// } /// }
/// ``` /// ```
pub fn application<State, Message, Theme, Renderer>( pub fn application<State, Message, Theme, Renderer>(
boot: impl Boot<State, Message>, boot: impl BootFn<State, Message>,
update: impl Update<State, Message>, update: impl UpdateFn<State, 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, Theme = Theme>> ) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
where where
State: 'static, State: 'static,
Message: Send + 'static, Message: Send + 'static,
Theme: Default + theme::Base, Theme: theme::Base,
Renderer: program::Renderer, Renderer: program::Renderer,
{ {
use std::marker::PhantomData; use std::marker::PhantomData;
@ -102,11 +103,11 @@ where
for Instance<State, Message, Theme, Renderer, Boot, Update, View> for Instance<State, Message, Theme, Renderer, Boot, Update, View>
where where
Message: Send + 'static, Message: Send + 'static,
Theme: Default + theme::Base, Theme: theme::Base,
Renderer: program::Renderer, Renderer: program::Renderer,
Boot: self::Boot<State, Message>, Boot: self::BootFn<State, Message>,
Update: self::Update<State, Message>, Update: self::UpdateFn<State, 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 State = State;
type Message = Message; type Message = Message;
@ -334,10 +335,10 @@ impl<P: Program> Application<P> {
} }
} }
/// Sets the [`Title`] of the [`Application`]. /// Sets the title of the [`Application`].
pub fn title( pub fn title(
self, self,
title: impl Title<P::State>, title: impl TitleFn<P::State>,
) -> Application< ) -> Application<
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
@ -369,12 +370,14 @@ impl<P: Program> Application<P> {
/// Sets the theme logic of the [`Application`]. /// Sets the theme logic of the [`Application`].
pub fn theme( pub fn theme(
self, self,
f: impl Fn(&P::State) -> P::Theme, f: impl ThemeFn<P::State, P::Theme>,
) -> Application< ) -> Application<
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
Application { Application {
raw: program::with_theme(self.raw, move |state, _window| f(state)), raw: program::with_theme(self.raw, move |state, _window| {
f.theme(state)
}),
settings: self.settings, settings: self.settings,
window: self.window, window: self.window,
presets: self.presets, presets: self.presets,
@ -399,7 +402,7 @@ impl<P: Program> Application<P> {
/// Sets the scale factor of the [`Application`]. /// Sets the scale factor of the [`Application`].
pub fn scale_factor( pub fn scale_factor(
self, self,
f: impl Fn(&P::State) -> f64, f: impl Fn(&P::State) -> f32,
) -> Application< ) -> Application<
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
@ -497,7 +500,7 @@ impl<P: Program> Program for Application<P> {
&self, &self,
state: &Self::State, state: &Self::State,
window: iced_core::window::Id, window: iced_core::window::Id,
) -> Self::Theme { ) -> Option<Self::Theme> {
debug::hot(|| self.raw.theme(state, window)) debug::hot(|| self.raw.theme(state, window))
} }
@ -505,7 +508,7 @@ impl<P: Program> Program for Application<P> {
debug::hot(|| self.raw.style(state, theme)) debug::hot(|| self.raw.style(state, theme))
} }
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
debug::hot(|| self.raw.scale_factor(state, window)) debug::hot(|| self.raw.scale_factor(state, window))
} }
@ -522,12 +525,12 @@ impl<P: Program> Program for Application<P> {
/// In practice, this means that [`application`] can both take /// In practice, this means that [`application`] can both take
/// simple functions like `State::default` and more advanced ones /// simple functions like `State::default` and more advanced ones
/// that return a [`Task`]. /// that return a [`Task`].
pub trait Boot<State, Message> { pub trait BootFn<State, Message> {
/// Initializes the [`Application`] state. /// Initializes the [`Application`] state.
fn boot(&self) -> (State, Task<Message>); 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 where
T: Fn() -> C, T: Fn() -> C,
C: IntoBoot<State, Message>, C: IntoBoot<State, Message>,
@ -561,18 +564,18 @@ impl<State, Message> IntoBoot<State, Message> for (State, Task<Message>) {
/// any closure `Fn(&State) -> String`. /// any closure `Fn(&State) -> String`.
/// ///
/// This trait allows the [`application`] builder to take any of them. /// 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`]. /// Produces the title of the [`Application`].
fn title(&self, state: &State) -> String; 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 { fn title(&self, _state: &State) -> String {
self.to_string() self.to_string()
} }
} }
impl<T, State> Title<State> for T impl<T, State> TitleFn<State> for T
where where
T: Fn(&State) -> String, T: Fn(&State) -> String,
{ {
@ -585,18 +588,18 @@ where
/// ///
/// This trait allows the [`application`] builder to take any closure that /// This trait allows the [`application`] builder to take any closure that
/// returns any `Into<Task<Message>>`. /// 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`]. /// Processes the message and updates the state of the [`Application`].
fn update(&self, state: &mut State, message: Message) -> Task<Message>; 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> { fn update(&self, _state: &mut State, _message: Message) -> Task<Message> {
Task::none() Task::none()
} }
} }
impl<T, State, Message, C> Update<State, Message> for T impl<T, State, Message, C> UpdateFn<State, Message> for T
where where
T: Fn(&mut State, Message) -> C, T: Fn(&mut State, Message) -> C,
C: Into<Task<Message>>, C: Into<Task<Message>>,
@ -610,13 +613,13 @@ where
/// ///
/// This trait allows the [`application`] builder to take any closure that /// This trait allows the [`application`] builder to take any closure that
/// returns any `Into<Element<'_, Message>>`. /// 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`]. /// Produces the widget of the [`Application`].
fn view(&self, state: &'a State) -> Element<'a, Message, Theme, Renderer>; fn view(&self, state: &'a State) -> Element<'a, Message, Theme, Renderer>;
} }
impl<'a, T, State, Message, Theme, Renderer, Widget> 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 where
T: Fn(&'a State) -> Widget, T: Fn(&'a State) -> Widget,
State: 'static, State: 'static,
@ -626,3 +629,35 @@ where
self(state).into() 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()
}
}

View file

@ -1,5 +1,5 @@
//! An [`Application`] that receives an [`Instant`] in update logic. //! 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::program;
use crate::theme; use crate::theme;
use crate::time::Instant; use crate::time::Instant;
@ -20,17 +20,17 @@ use iced_debug as debug;
/// ///
/// [`comet`]: https://github.com/iced-rs/comet /// [`comet`]: https://github.com/iced-rs/comet
pub fn timed<State, Message, Theme, Renderer>( pub fn timed<State, Message, Theme, Renderer>(
boot: impl Boot<State, Message>, boot: impl BootFn<State, Message>,
update: impl Update<State, Message>, update: impl UpdateFn<State, Message>,
subscription: impl Fn(&State) -> Subscription<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< ) -> Application<
impl Program<State = State, Message = (Message, Instant), Theme = Theme>, impl Program<State = State, Message = (Message, Instant), Theme = Theme>,
> >
where where
State: 'static, State: 'static,
Message: Send + 'static, Message: Send + 'static,
Theme: Default + theme::Base + 'static, Theme: theme::Base + 'static,
Renderer: program::Renderer + 'static, Renderer: program::Renderer + 'static,
{ {
use std::marker::PhantomData; use std::marker::PhantomData;
@ -69,12 +69,12 @@ where
> >
where where
Message: Send + 'static, Message: Send + 'static,
Theme: Default + theme::Base + 'static, Theme: theme::Base + 'static,
Renderer: program::Renderer + 'static, Renderer: program::Renderer + 'static,
Boot: self::Boot<State, Message>, Boot: self::BootFn<State, Message>,
Update: self::Update<State, Message>, Update: self::UpdateFn<State, Message>,
Subscription: Fn(&State) -> self::Subscription<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 State = State;
type Message = (Message, Instant); type Message = (Message, Instant);
@ -157,9 +157,9 @@ where
/// The update logic of some timed [`Application`]. /// 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`]. /// 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`]. /// Processes the message and updates the state of the [`Application`].
fn update( fn update(
&self, &self,
@ -169,7 +169,7 @@ pub trait Update<State, Message> {
) -> impl Into<Task<Message>>; ) -> impl Into<Task<Message>>;
} }
impl<State, Message> Update<State, Message> for () { impl<State, Message> UpdateFn<State, Message> for () {
fn update( fn update(
&self, &self,
_state: &mut State, _state: &mut State,
@ -179,7 +179,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 where
T: Fn(&mut State, Message, Instant) -> C, T: Fn(&mut State, Message, Instant) -> C,
C: Into<Task<Message>>, C: Into<Task<Message>>,

View file

@ -7,6 +7,7 @@ use crate::theme;
use crate::window; use crate::window;
use crate::{ use crate::{
Element, Executor, Font, Preset, Result, Settings, Subscription, Task, Element, Executor, Font, Preset, Result, Settings, Subscription, Task,
Theme,
}; };
use iced_debug as debug; use iced_debug as debug;
@ -24,14 +25,14 @@ use std::borrow::Cow;
/// ///
/// [`exit`]: crate::exit /// [`exit`]: crate::exit
pub fn daemon<State, Message, Theme, Renderer>( pub fn daemon<State, Message, Theme, Renderer>(
boot: impl application::Boot<State, Message>, boot: impl application::BootFn<State, Message>,
update: impl application::Update<State, Message>, update: impl application::UpdateFn<State, Message>,
view: impl for<'a> View<'a, State, Message, Theme, Renderer>, view: impl for<'a> ViewFn<'a, State, Message, Theme, Renderer>,
) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>> ) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>>
where where
State: 'static, State: 'static,
Message: Send + 'static, Message: Send + 'static,
Theme: Default + theme::Base, Theme: theme::Base,
Renderer: program::Renderer, Renderer: program::Renderer,
{ {
use std::marker::PhantomData; use std::marker::PhantomData;
@ -50,11 +51,11 @@ where
for Instance<State, Message, Theme, Renderer, Boot, Update, View> for Instance<State, Message, Theme, Renderer, Boot, Update, View>
where where
Message: Send + 'static, Message: Send + 'static,
Theme: Default + theme::Base, Theme: theme::Base,
Renderer: program::Renderer, Renderer: program::Renderer,
Boot: application::Boot<State, Message>, Boot: application::BootFn<State, Message>,
Update: application::Update<State, Message>, Update: application::UpdateFn<State, 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 State = State;
type Message = Message; type Message = Message;
@ -183,10 +184,10 @@ impl<P: Program> Daemon<P> {
self self
} }
/// Sets the [`Title`] of the [`Daemon`]. /// Sets the title of the [`Daemon`].
pub fn title( pub fn title(
self, self,
title: impl Title<P::State>, title: impl TitleFn<P::State>,
) -> Daemon< ) -> Daemon<
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
@ -216,12 +217,14 @@ impl<P: Program> Daemon<P> {
/// Sets the theme logic of the [`Daemon`]. /// Sets the theme logic of the [`Daemon`].
pub fn theme( pub fn theme(
self, self,
f: impl Fn(&P::State, window::Id) -> P::Theme, f: impl ThemeFn<P::State, P::Theme>,
) -> Daemon< ) -> Daemon<
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
Daemon { Daemon {
raw: program::with_theme(self.raw, f), raw: program::with_theme(self.raw, move |state, window| {
f.theme(state, window)
}),
settings: self.settings, settings: self.settings,
presets: self.presets, presets: self.presets,
} }
@ -244,7 +247,7 @@ impl<P: Program> Daemon<P> {
/// Sets the scale factor of the [`Daemon`]. /// Sets the scale factor of the [`Daemon`].
pub fn scale_factor( pub fn scale_factor(
self, self,
f: impl Fn(&P::State, window::Id) -> f64, f: impl Fn(&P::State, window::Id) -> f32,
) -> Daemon< ) -> Daemon<
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> { > {
@ -338,7 +341,7 @@ impl<P: Program> Program for Daemon<P> {
&self, &self,
state: &Self::State, state: &Self::State,
window: iced_core::window::Id, window: iced_core::window::Id,
) -> Self::Theme { ) -> Option<Self::Theme> {
debug::hot(|| self.raw.theme(state, window)) debug::hot(|| self.raw.theme(state, window))
} }
@ -346,7 +349,7 @@ impl<P: Program> Program for Daemon<P> {
debug::hot(|| self.raw.style(state, theme)) debug::hot(|| self.raw.style(state, theme))
} }
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
debug::hot(|| self.raw.scale_factor(state, window)) debug::hot(|| self.raw.scale_factor(state, window))
} }
@ -361,18 +364,18 @@ impl<P: Program> Program for Daemon<P> {
/// any closure `Fn(&State, window::Id) -> String`. /// any closure `Fn(&State, window::Id) -> String`.
/// ///
/// This trait allows the [`daemon`] builder to take any of them. /// 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`]. /// Produces the title of the [`Daemon`].
fn title(&self, state: &State, window: window::Id) -> String; 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 { fn title(&self, _state: &State, _window: window::Id) -> String {
self.to_string() self.to_string()
} }
} }
impl<T, State> Title<State> for T impl<T, State> TitleFn<State> for T
where where
T: Fn(&State, window::Id) -> String, T: Fn(&State, window::Id) -> String,
{ {
@ -385,7 +388,7 @@ where
/// ///
/// This trait allows the [`daemon`] builder to take any closure that /// This trait allows the [`daemon`] builder to take any closure that
/// returns any `Into<Element<'_, Message>>`. /// 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`]. /// Produces the widget of the [`Daemon`].
fn view( fn view(
&self, &self,
@ -395,7 +398,7 @@ pub trait View<'a, State, Message, Theme, Renderer> {
} }
impl<'a, T, State, Message, Theme, Renderer, Widget> 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 where
T: Fn(&'a State, window::Id) -> Widget, T: Fn(&'a State, window::Id) -> Widget,
State: 'static, State: 'static,
@ -409,3 +412,35 @@ where
self(state, window).into() 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()
}
}

View file

@ -589,11 +589,12 @@ pub mod mouse {
}; };
} }
#[cfg(feature = "system")]
pub mod system { pub mod system {
//! Retrieve system information. //! Retrieve system information.
pub use crate::runtime::system::Information; pub use crate::runtime::system::{theme, theme_changes};
pub use crate::shell::system::*;
#[cfg(feature = "sysinfo")]
pub use crate::runtime::system::{Information, information};
} }
pub mod overlay { pub mod overlay {
@ -691,14 +692,14 @@ pub type Result = std::result::Result<(), Error>;
/// } /// }
/// ``` /// ```
pub fn run<State, Message, Theme, Renderer>( pub fn run<State, Message, Theme, Renderer>(
update: impl application::Update<State, Message> + 'static, update: impl application::UpdateFn<State, Message> + 'static,
view: impl for<'a> application::View<'a, State, Message, Theme, Renderer> view: impl for<'a> application::ViewFn<'a, State, Message, Theme, Renderer>
+ 'static, + 'static,
) -> Result ) -> Result
where where
State: Default + 'static, State: Default + 'static,
Message: Send + message::MaybeDebug + message::MaybeClone + 'static, Message: Send + message::MaybeDebug + message::MaybeClone + 'static,
Theme: Default + theme::Base + 'static, Theme: theme::Base + 'static,
Renderer: program::Renderer + 'static, Renderer: program::Renderer + 'static,
{ {
application(State::default, update, view).run() application(State::default, update, view).run()

View file

@ -11,3 +11,17 @@ pub use crate::core::time::*;
))) )))
)] )]
pub use iced_futures::backend::default::time::*; pub use iced_futures::backend::default::time::*;
use crate::Task;
/// Returns a [`Task`] that produces the current [`Instant`]
/// by calling [`Instant::now`].
///
/// While you can call [`Instant::now`] directly in your application;
/// that renders your application "impure" (i.e. no referential transparency).
///
/// You may care about purity if you want to leverage the `time-travel`
/// feature properly.
pub fn now() -> Task<Instant> {
Task::future(async { Instant::now() })
}

View file

@ -379,7 +379,7 @@ impl<P: Program + 'static> Emulator<P> {
program.view(&self.state, self.window) program.view(&self.state, self.window)
} }
pub fn theme(&self, program: &P) -> P::Theme { pub fn theme(&self, program: &P) -> Option<P::Theme> {
program.theme(&self.state, self.window) program.theme(&self.state, self.window)
} }

View file

@ -18,6 +18,7 @@ use crate::{Error, Selector};
use std::borrow::Cow; use std::borrow::Cow;
use std::env; use std::env;
use std::fs; use std::fs;
use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
@ -235,7 +236,7 @@ where
screenshot: window::Screenshot::new( screenshot: window::Screenshot::new(
rgba, rgba,
physical_size, physical_size,
f64::from(scale_factor), scale_factor,
), ),
renderer: self.renderer.name(), renderer: self.renderer.name(),
}) })
@ -267,10 +268,13 @@ impl Snapshot {
if path.exists() { if path.exists() {
let file = fs::File::open(&path)?; 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 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)?; let info = reader.next_frame(&mut bytes)?;
Ok(self.screenshot.bytes == bytes[..info.buffer_size()]) Ok(self.screenshot.bytes == bytes[..info.buffer_size()])

View file

@ -566,12 +566,16 @@ impl<P: Program + 'static> Tester<P> {
let viewport = container( let viewport = container(
scrollable( scrollable(
container(match &self.state { container(match &self.state {
State::Empty => horizontal_space().into(), State::Empty => Element::from(horizontal_space()),
State::Idle { state } => Element::from(themer( State::Idle { state } => {
program.theme(state, window), let theme = program.theme(state, window);
program.view(state, window),
)) themer(
.map(Tick::Program), theme,
program.view(state, window).map(Tick::Program),
)
.into()
}
State::Recording { emulator } => { State::Recording { emulator } => {
let theme = emulator.theme(program); let theme = emulator.theme(program);
let view = emulator.view(program).map(Tick::Program); let view = emulator.view(program).map(Tick::Program);

View file

@ -72,7 +72,7 @@ impl Renderer {
damage: &[Rectangle], damage: &[Rectangle],
background_color: Color, background_color: Color,
) { ) {
let scale_factor = viewport.scale_factor() as f32; let scale_factor = viewport.scale_factor();
self.layers.flush(); self.layers.flush();
@ -404,8 +404,7 @@ impl renderer::Headless for Renderer {
scale_factor: f32, scale_factor: f32,
background_color: Color, background_color: Color,
) -> Vec<u8> { ) -> Vec<u8> {
let viewport = let viewport = Viewport::with_physical_size(size, scale_factor);
Viewport::with_physical_size(size, f64::from(scale_factor));
window::compositor::screenshot(self, &viewport, background_color) window::compositor::screenshot(self, &viewport, background_color)
} }

View file

@ -100,7 +100,7 @@ impl crate::graphics::Compositor for Compositor {
surface.layer_stack.clear(); surface.layer_stack.clear();
} }
fn fetch_information(&self) -> Information { fn information(&self) -> Information {
Information { Information {
adapter: String::from("CPU"), adapter: String::from("CPU"),
backend: String::from("tiny-skia"), backend: String::from("tiny-skia"),

View file

@ -235,13 +235,13 @@ impl Layer {
pub fn draw_primitive( pub fn draw_primitive(
&mut self, &mut self,
bounds: Rectangle, bounds: Rectangle,
primitive: Box<dyn Primitive>, primitive: impl Primitive,
transformation: Transformation, transformation: Transformation,
) { ) {
let bounds = bounds * transformation; let bounds = bounds * transformation;
self.primitives self.primitives
.push(primitive::Instance { bounds, primitive }); .push(primitive::Instance::new(bounds, primitive));
} }
fn flush_meshes(&mut self) { fn flush_meshes(&mut self) {

View file

@ -302,7 +302,7 @@ impl Renderer {
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
viewport: &Viewport, viewport: &Viewport,
) { ) {
let scale_factor = viewport.scale_factor() as f32; let scale_factor = viewport.scale_factor();
self.text_viewport self.text_viewport
.update(&self.engine.queue, viewport.physical_size()); .update(&self.engine.queue, viewport.physical_size());
@ -365,10 +365,10 @@ impl Renderer {
for instance in &layer.primitives { for instance in &layer.primitives {
instance.primitive.prepare( instance.primitive.prepare(
&mut primitive_storage,
&self.engine.device, &self.engine.device,
&self.engine.queue, &self.engine.queue,
self.engine.format, self.engine.format,
&mut primitive_storage,
&instance.bounds, &instance.bounds,
viewport, viewport,
); );
@ -464,7 +464,7 @@ impl Renderer {
#[cfg(any(feature = "svg", feature = "image"))] #[cfg(any(feature = "svg", feature = "image"))]
let image_cache = self.image_cache.borrow(); let image_cache = self.image_cache.borrow();
let scale_factor = viewport.scale_factor() as f32; let scale_factor = viewport.scale_factor();
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size( let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
viewport.physical_size(), viewport.physical_size(),
)); ));
@ -534,7 +534,6 @@ impl Renderer {
if !layer.primitives.is_empty() { if !layer.primitives.is_empty() {
let render_span = debug::render(debug::Primitive::Shader); let render_span = debug::render(debug::Primitive::Shader);
let _ = ManuallyDrop::into_inner(render_pass);
let primitive_storage = self let primitive_storage = self
.engine .engine
@ -542,41 +541,91 @@ impl Renderer {
.read() .read()
.expect("Read primitive storage"); .expect("Read primitive storage");
let mut need_render = Vec::new();
for instance in &layer.primitives { for instance in &layer.primitives {
let bounds = instance.bounds * scale;
if let Some(clip_bounds) = (instance.bounds * scale) if let Some(clip_bounds) = (instance.bounds * scale)
.intersection(&physical_bounds) .intersection(&physical_bounds)
.and_then(Rectangle::snap) .and_then(Rectangle::snap)
{ {
render_pass.set_viewport(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
0.0,
1.0,
);
render_pass.set_scissor_rect(
clip_bounds.x,
clip_bounds.y,
clip_bounds.width,
clip_bounds.height,
);
let drawn = instance
.primitive
.draw(&primitive_storage, &mut render_pass);
if !drawn {
need_render.push((instance, clip_bounds));
}
}
}
render_pass.set_viewport(
0.0,
0.0,
viewport.physical_width() as f32,
viewport.physical_height() as f32,
0.0,
1.0,
);
render_pass.set_scissor_rect(
0,
0,
viewport.physical_width(),
viewport.physical_height(),
);
if !need_render.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass);
for (instance, clip_bounds) in need_render {
instance.primitive.render( instance.primitive.render(
encoder,
&primitive_storage, &primitive_storage,
encoder,
frame, frame,
&clip_bounds, &clip_bounds,
); );
} }
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: frame,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
} }
render_span.finish(); render_span.finish();
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: frame,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
} }
#[cfg(any(feature = "svg", feature = "image"))] #[cfg(any(feature = "svg", feature = "image"))]
@ -804,7 +853,7 @@ impl graphics::geometry::Renderer for Renderer {
impl primitive::Renderer for Renderer { impl primitive::Renderer for Renderer {
fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) { fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
layer.draw_primitive(bounds, Box::new(primitive), transformation); layer.draw_primitive(bounds, primitive, transformation);
} }
} }
@ -878,7 +927,7 @@ impl renderer::Headless for Renderer {
background_color: Color, background_color: Color,
) -> Vec<u8> { ) -> Vec<u8> {
self.screenshot( self.screenshot(
&Viewport::with_physical_size(size, f64::from(scale_factor)), &Viewport::with_physical_size(size, scale_factor),
background_color, background_color,
) )
} }

View file

@ -1,6 +1,7 @@
//! Draw custom primitives. //! Draw custom primitives.
use crate::core::{self, Rectangle}; use crate::core::{self, Rectangle};
use crate::graphics::Viewport; use crate::graphics::Viewport;
use crate::graphics::futures::{MaybeSend, MaybeSync};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
@ -10,36 +11,172 @@ use std::fmt::Debug;
pub type Batch = Vec<Instance>; pub type Batch = Vec<Instance>;
/// A set of methods which allows a [`Primitive`] to be rendered. /// A set of methods which allows a [`Primitive`] to be rendered.
pub trait Primitive: Debug + Send + Sync + 'static { pub trait Primitive: Debug + MaybeSend + MaybeSync + 'static {
/// Processes the [`Primitive`], allowing for GPU buffer allocation. /// The shared renderer of this [`Primitive`].
fn prepare( ///
/// Normally, this will contain a bunch of [`wgpu`] state; like
/// a rendering pipeline, buffers, and textures.
///
/// All instances of this [`Primitive`] type will share the same
/// [`Renderer`].
type Renderer: MaybeSend + MaybeSync;
/// Initializes the [`Renderer`](Self::Renderer) of the [`Primitive`].
///
/// This will only be called once, when the first [`Primitive`] of this kind
/// is encountered.
fn initialize(
&self, &self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
storage: &mut Storage, ) -> Self::Renderer;
/// Processes the [`Primitive`], allowing for GPU buffer allocation.
fn prepare(
&self,
renderer: &mut Self::Renderer,
device: &wgpu::Device,
queue: &wgpu::Queue,
bounds: &Rectangle, bounds: &Rectangle,
viewport: &Viewport, viewport: &Viewport,
); );
/// Renders the [`Primitive`]. /// Draws the [`Primitive`] in the given [`wgpu::RenderPass`].
///
/// When possible, this should be implemented over [`render`](Self::render)
/// since reusing the existing render pass should be considerably more
/// efficient than issuing a new one.
///
/// The viewport and scissor rect of the render pass provided is set
/// to the bounds and clip bounds of the [`Primitive`], respectively.
///
/// If you have complex composition needs, then you can leverage
/// [`render`](Self::render) by returning `false` here.
///
/// By default, it does nothing and returns `false`.
fn draw(
&self,
_renderer: &Self::Renderer,
_render_pass: &mut wgpu::RenderPass<'_>,
) -> bool {
false
}
/// Renders the [`Primitive`], using the given [`wgpu::CommandEncoder`].
///
/// This will only be called if [`draw`](Self::draw) returns `false`.
///
/// By default, it does nothing.
fn render( fn render(
&self, &self,
encoder: &mut wgpu::CommandEncoder, _renderer: &Self::Renderer,
_encoder: &mut wgpu::CommandEncoder,
_target: &wgpu::TextureView,
_clip_bounds: &Rectangle<u32>,
) {
}
}
pub(crate) trait Stored:
Debug + MaybeSend + MaybeSync + 'static
{
fn prepare(
&self,
storage: &mut Storage,
device: &wgpu::Device,
queue: &wgpu::Queue,
format: wgpu::TextureFormat,
bounds: &Rectangle,
viewport: &Viewport,
);
fn draw(
&self,
storage: &Storage, storage: &Storage,
render_pass: &mut wgpu::RenderPass<'_>,
) -> bool;
fn render(
&self,
storage: &Storage,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView, target: &wgpu::TextureView,
clip_bounds: &Rectangle<u32>, clip_bounds: &Rectangle<u32>,
); );
} }
#[derive(Debug)]
struct BlackBox<P: Primitive> {
primitive: P,
}
impl<P: Primitive> Stored for BlackBox<P> {
fn prepare(
&self,
storage: &mut Storage,
device: &wgpu::Device,
queue: &wgpu::Queue,
format: wgpu::TextureFormat,
bounds: &Rectangle,
viewport: &Viewport,
) {
if !storage.has::<P>() {
storage.store::<P, _>(
self.primitive.initialize(device, queue, format),
);
}
let renderer = storage
.get_mut::<P>()
.expect("renderer should be initialized")
.downcast_mut::<P::Renderer>()
.expect("renderer should have the proper type");
self.primitive
.prepare(renderer, device, queue, bounds, viewport);
}
fn draw(
&self,
storage: &Storage,
render_pass: &mut wgpu::RenderPass<'_>,
) -> bool {
let renderer = storage
.get::<P>()
.expect("renderer should be initialized")
.downcast_ref::<P::Renderer>()
.expect("renderer should have the proper type");
self.primitive.draw(renderer, render_pass)
}
fn render(
&self,
storage: &Storage,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
clip_bounds: &Rectangle<u32>,
) {
let renderer = storage
.get::<P>()
.expect("renderer should be initialized")
.downcast_ref::<P::Renderer>()
.expect("renderer should have the proper type");
self.primitive
.render(renderer, encoder, target, clip_bounds);
}
}
#[derive(Debug)] #[derive(Debug)]
/// An instance of a specific [`Primitive`]. /// An instance of a specific [`Primitive`].
pub struct Instance { pub struct Instance {
/// The bounds of the [`Instance`]. /// The bounds of the [`Instance`].
pub bounds: Rectangle, pub(crate) bounds: Rectangle,
/// The [`Primitive`] to render. /// The [`Primitive`] to render.
pub primitive: Box<dyn Primitive>, pub(crate) primitive: Box<dyn Stored>,
} }
impl Instance { impl Instance {
@ -47,7 +184,7 @@ impl Instance {
pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self {
Instance { Instance {
bounds, bounds,
primitive: Box::new(primitive), primitive: Box::new(BlackBox { primitive }),
} }
} }
} }
@ -59,9 +196,10 @@ pub trait Renderer: core::Renderer {
} }
/// Stores custom, user-provided types. /// Stores custom, user-provided types.
#[derive(Default, Debug)] #[derive(Default)]
#[allow(missing_debug_implementations)]
pub struct Storage { pub struct Storage {
pipelines: FxHashMap<TypeId, Box<dyn Any + Send + Sync>>, pipelines: FxHashMap<TypeId, Box<dyn AnyConcurrent>>,
} }
impl Storage { impl Storage {
@ -71,25 +209,28 @@ impl Storage {
} }
/// Inserts the data `T` in to [`Storage`]. /// Inserts the data `T` in to [`Storage`].
pub fn store<T: 'static + Send + Sync>(&mut self, data: T) { pub fn store<T: 'static, D: Any + MaybeSend + MaybeSync>(
&mut self,
data: D,
) {
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(data)); let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(data));
} }
/// Returns a reference to the data with type `T` if it exists in [`Storage`]. /// Returns a reference to the data with type `T` if it exists in [`Storage`].
pub fn get<T: 'static>(&self) -> Option<&T> { pub fn get<T: 'static>(&self) -> Option<&dyn Any> {
self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| { self.pipelines
pipeline .get(&TypeId::of::<T>())
.downcast_ref::<T>() .map(|pipeline| pipeline.as_ref() as &dyn Any)
.expect("Value with this type does not exist in Storage.")
})
} }
/// Returns a mutable reference to the data with type `T` if it exists in [`Storage`]. /// Returns a mutable reference to the data with type `T` if it exists in [`Storage`].
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> { pub fn get_mut<T: 'static>(&mut self) -> Option<&mut dyn Any> {
self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| { self.pipelines
pipeline .get_mut(&TypeId::of::<T>())
.downcast_mut::<T>() .map(|pipeline| pipeline.as_mut() as &mut dyn Any)
.expect("Value with this type does not exist in Storage.")
})
} }
} }
trait AnyConcurrent: Any + MaybeSend + MaybeSync {}
impl<T> AnyConcurrent for T where T: Any + MaybeSend + MaybeSync {}

View file

@ -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(); let information = self.adapter.get_info();
compositor::Information { compositor::Information {

View file

@ -4,6 +4,7 @@ use crate::checkbox::{self, Checkbox};
use crate::combo_box::{self, ComboBox}; use crate::combo_box::{self, ComboBox};
use crate::container::{self, Container}; use crate::container::{self, Container};
use crate::core; use crate::core;
use crate::core::theme;
use crate::core::widget::operation::{self, Operation}; use crate::core::widget::operation::{self, Operation};
use crate::core::window; use crate::core::window;
use crate::core::{Element, Length, Pixels, Size, Widget}; use crate::core::{Element, Length, Pixels, Size, Widget};
@ -2050,10 +2051,11 @@ where
/// A widget that applies any `Theme` to its contents. /// A widget that applies any `Theme` to its contents.
pub fn themer<'a, Message, Theme, Renderer>( pub fn themer<'a, Message, Theme, Renderer>(
theme: Theme, theme: Option<Theme>,
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Themer<'a, Message, Theme, Renderer> ) -> Themer<'a, Message, Theme, Renderer>
where where
Theme: theme::Base,
Renderer: core::Renderer, Renderer: core::Renderer,
{ {
Themer::new(theme, content) Themer::new(theme, content)

View file

@ -73,7 +73,7 @@ where
padding: Padding::ZERO, padding: Padding::ZERO,
text_size: None, text_size: None,
text_line_height: text::LineHeight::default(), text_line_height: text::LineHeight::default(),
text_shaping: text::Shaping::Basic, text_shaping: text::Shaping::default(),
font: None, font: None,
class, class,
} }

View file

@ -3,6 +3,7 @@ use crate::core::layout;
use crate::core::mouse; use crate::core::mouse;
use crate::core::overlay; use crate::core::overlay;
use crate::core::renderer; use crate::core::renderer;
use crate::core::theme;
use crate::core::widget::Operation; use crate::core::widget::Operation;
use crate::core::widget::tree::{self, Tree}; use crate::core::widget::tree::{self, Tree};
use crate::core::{ use crate::core::{
@ -20,7 +21,7 @@ where
Renderer: crate::core::Renderer, Renderer: crate::core::Renderer,
{ {
content: Element<'a, Message, Theme, Renderer>, content: Element<'a, Message, Theme, Renderer>,
theme: Theme, theme: Option<Theme>,
text_color: Option<fn(&Theme) -> Color>, text_color: Option<fn(&Theme) -> Color>,
background: Option<fn(&Theme) -> Background>, background: Option<fn(&Theme) -> Background>,
} }
@ -32,7 +33,7 @@ where
/// Creates an empty [`Themer`] that applies the given `Theme` /// Creates an empty [`Themer`] that applies the given `Theme`
/// to the provided `content`. /// to the provided `content`.
pub fn new( pub fn new(
theme: Theme, theme: Option<Theme>,
content: impl Into<Element<'a, Message, Theme, Renderer>>, content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Self { ) -> Self {
Self { Self {
@ -59,6 +60,8 @@ where
impl<Message, Theme, Renderer, AnyTheme> Widget<Message, AnyTheme, Renderer> impl<Message, Theme, Renderer, AnyTheme> Widget<Message, AnyTheme, Renderer>
for Themer<'_, Message, Theme, Renderer> for Themer<'_, Message, Theme, Renderer>
where where
Theme: theme::Base,
AnyTheme: theme::Base,
Renderer: crate::core::Renderer, Renderer: crate::core::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
@ -135,17 +138,20 @@ where
&self, &self,
tree: &Tree, tree: &Tree,
renderer: &mut Renderer, renderer: &mut Renderer,
_theme: &AnyTheme, theme: &AnyTheme,
style: &renderer::Style, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
let default_theme = theme::Base::default(theme.mode());
let theme = self.theme.as_ref().unwrap_or(&default_theme);
if let Some(background) = self.background { if let Some(background) = self.background {
container::draw_background( container::draw_background(
renderer, renderer,
&container::Style { &container::Style {
background: Some(background(&self.theme)), background: Some(background(theme)),
..container::Style::default() ..container::Style::default()
}, },
layout.bounds(), layout.bounds(),
@ -154,21 +160,15 @@ where
let style = if let Some(text_color) = self.text_color { let style = if let Some(text_color) = self.text_color {
renderer::Style { renderer::Style {
text_color: text_color(&self.theme), text_color: text_color(theme),
} }
} else { } else {
*style *style
}; };
self.content.as_widget().draw( self.content
tree, .as_widget()
renderer, .draw(tree, renderer, theme, &style, layout, cursor, viewport);
&self.theme,
&style,
layout,
cursor,
viewport,
);
} }
fn overlay<'b>( fn overlay<'b>(
@ -180,7 +180,7 @@ where
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, AnyTheme, Renderer>> { ) -> Option<overlay::Element<'b, Message, AnyTheme, Renderer>> {
struct Overlay<'a, Message, Theme, Renderer> { struct Overlay<'a, Message, Theme, Renderer> {
theme: &'a Theme, theme: &'a Option<Theme>,
content: overlay::Element<'a, Message, Theme, Renderer>, content: overlay::Element<'a, Message, Theme, Renderer>,
} }
@ -188,6 +188,8 @@ where
overlay::Overlay<Message, AnyTheme, Renderer> overlay::Overlay<Message, AnyTheme, Renderer>
for Overlay<'_, Message, Theme, Renderer> for Overlay<'_, Message, Theme, Renderer>
where where
Theme: theme::Base,
AnyTheme: theme::Base,
Renderer: crate::core::Renderer, Renderer: crate::core::Renderer,
{ {
fn layout( fn layout(
@ -201,14 +203,17 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
_theme: &AnyTheme, theme: &AnyTheme,
style: &renderer::Style, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
) { ) {
let default_theme = theme::Base::default(theme.mode());
let theme = self.theme.as_ref().unwrap_or(&default_theme);
self.content self.content
.as_overlay() .as_overlay()
.draw(renderer, self.theme, style, layout, cursor); .draw(renderer, theme, style, layout, cursor);
} }
fn update( fn update(
@ -280,7 +285,8 @@ impl<'a, Message, Theme, Renderer, AnyTheme>
for Element<'a, Message, AnyTheme, Renderer> for Element<'a, Message, AnyTheme, Renderer>
where where
Message: 'a, Message: 'a,
Theme: 'a, Theme: theme::Base + 'a,
AnyTheme: theme::Base,
Renderer: 'a + crate::core::Renderer, Renderer: 'a + crate::core::Renderer,
{ {
fn from( fn from(

View file

@ -214,11 +214,9 @@ where
let is_idle = *state == State::Idle; let is_idle = *state == State::Idle;
if was_idle != is_idle { if was_idle != is_idle
shell.invalidate_layout(); || (self.position == Position::FollowCursor
shell.request_redraw(); && previous_state != *state)
} else if self.position == Position::FollowCursor
&& previous_state != *state
{ {
shell.request_redraw(); shell.request_redraw();
} }

View file

@ -16,12 +16,13 @@ workspace = true
[features] [features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
debug = ["iced_debug/enable"] debug = ["iced_debug/enable"]
system = ["sysinfo"] sysinfo = ["dep:sysinfo"]
x11 = ["winit/x11"] x11 = ["winit/x11"]
wayland = ["winit/wayland"] wayland = ["winit/wayland"]
wayland-dlopen = ["winit/wayland-dlopen"] wayland-dlopen = ["winit/wayland-dlopen"]
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"] wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
unconditional-rendering = [] unconditional-rendering = []
linux-theme-detection = ["dep:mundy", "mundy/async-io", "mundy/color-scheme"]
[dependencies] [dependencies]
iced_debug.workspace = true iced_debug.workspace = true
@ -41,3 +42,7 @@ sysinfo.optional = true
web-sys.workspace = true web-sys.workspace = true
web-sys.features = ["Document", "Window", "HtmlCanvasElement"] web-sys.features = ["Document", "Window", "HtmlCanvasElement"]
wasm-bindgen-futures.workspace = true wasm-bindgen-futures.workspace = true
[target.'cfg(target_os = "linux")'.dependencies]
mundy.workspace = true
mundy.optional = true

View file

@ -5,6 +5,7 @@
use crate::core::input_method; use crate::core::input_method;
use crate::core::keyboard; use crate::core::keyboard;
use crate::core::mouse; use crate::core::mouse;
use crate::core::theme;
use crate::core::touch; use crate::core::touch;
use crate::core::window; use crate::core::window;
use crate::core::{Event, Point, Size}; use crate::core::{Event, Point, Size};
@ -13,6 +14,7 @@ use crate::core::{Event, Point, Size};
pub fn window_attributes( pub fn window_attributes(
settings: window::Settings, settings: window::Settings,
title: &str, title: &str,
scale_factor: f32,
primary_monitor: Option<winit::monitor::MonitorHandle>, primary_monitor: Option<winit::monitor::MonitorHandle>,
_id: Option<String>, _id: Option<String>,
) -> winit::window::WindowAttributes { ) -> winit::window::WindowAttributes {
@ -21,8 +23,8 @@ pub fn window_attributes(
attributes = attributes attributes = attributes
.with_title(title) .with_title(title)
.with_inner_size(winit::dpi::LogicalSize { .with_inner_size(winit::dpi::LogicalSize {
width: settings.size.width, width: settings.size.width * scale_factor,
height: settings.size.height, height: settings.size.height * scale_factor,
}) })
.with_maximized(settings.maximized) .with_maximized(settings.maximized)
.with_fullscreen( .with_fullscreen(
@ -138,7 +140,7 @@ pub fn window_attributes(
/// Converts a winit window event into an iced event. /// Converts a winit window event into an iced event.
pub fn window_event( pub fn window_event(
event: winit::event::WindowEvent, event: winit::event::WindowEvent,
scale_factor: f64, scale_factor: f32,
modifiers: winit::keyboard::ModifiersState, modifiers: winit::keyboard::ModifiersState,
) -> Option<Event> { ) -> Option<Event> {
use winit::event::Ime; use winit::event::Ime;
@ -146,7 +148,7 @@ pub fn window_event(
match event { match event {
WindowEvent::Resized(new_size) => { WindowEvent::Resized(new_size) => {
let logical_size = new_size.to_logical(scale_factor); let logical_size = new_size.to_logical(f64::from(scale_factor));
Some(Event::Window(window::Event::Resized(Size { Some(Event::Window(window::Event::Resized(Size {
width: logical_size.width, width: logical_size.width,
@ -157,7 +159,7 @@ pub fn window_event(
Some(Event::Window(window::Event::CloseRequested)) Some(Event::Window(window::Event::CloseRequested))
} }
WindowEvent::CursorMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
let position = position.to_logical::<f64>(scale_factor); let position = position.to_logical::<f64>(f64::from(scale_factor));
Some(Event::Mouse(mouse::Event::CursorMoved { Some(Event::Mouse(mouse::Event::CursorMoved {
position: Point::new(position.x as f32, position.y as f32), position: Point::new(position.x as f32, position.y as f32),
@ -313,7 +315,7 @@ pub fn window_event(
} }
WindowEvent::Moved(position) => { WindowEvent::Moved(position) => {
let winit::dpi::LogicalPosition { x, y } = let winit::dpi::LogicalPosition { x, y } =
position.to_logical(scale_factor); position.to_logical(f64::from(scale_factor));
Some(Event::Window(window::Event::Moved(Point::new(x, y)))) Some(Event::Window(window::Event::Moved(Point::new(x, y))))
} }
@ -321,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 /// [`winit`]: https://github.com/rust-windowing/winit
pub fn window_level(level: window::Level) -> winit::window::WindowLevel { pub fn window_level(level: window::Level) -> winit::window::WindowLevel {
@ -334,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 /// [`winit`]: https://github.com/rust-windowing/winit
pub fn position( pub fn position(
@ -406,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 /// [`winit`]: https://github.com/rust-windowing/winit
pub fn fullscreen( pub fn fullscreen(
@ -421,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 { pub fn visible(mode: window::Mode) -> bool {
match mode { match mode {
window::Mode::Windowed | window::Mode::Fullscreen => true, window::Mode::Windowed | window::Mode::Fullscreen => true,
@ -429,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 /// [`winit`]: https://github.com/rust-windowing/winit
pub fn mode(mode: Option<winit::window::Fullscreen>) -> window::Mode { pub fn mode(mode: Option<winit::window::Fullscreen>) -> window::Mode {
@ -439,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 /// [`winit`]: https://github.com/rust-windowing/winit
pub fn mouse_interaction( pub fn mouse_interaction(
@ -510,12 +533,12 @@ pub fn modifiers(
result result
} }
/// Converts a physical cursor position to a logical `Point`. /// Converts a physical cursor position into a logical `Point`.
pub fn cursor_position( pub fn cursor_position(
position: winit::dpi::PhysicalPosition<f64>, position: winit::dpi::PhysicalPosition<f64>,
scale_factor: f64, scale_factor: f32,
) -> Point { ) -> Point {
let logical_position = position.to_logical(scale_factor); let logical_position = position.to_logical(f64::from(scale_factor));
Point::new(logical_position.x, logical_position.y) Point::new(logical_position.x, logical_position.y)
} }
@ -526,11 +549,12 @@ pub fn cursor_position(
/// [`iced`]: https://github.com/iced-rs/iced/tree/0.12 /// [`iced`]: https://github.com/iced-rs/iced/tree/0.12
pub fn touch_event( pub fn touch_event(
touch: winit::event::Touch, touch: winit::event::Touch,
scale_factor: f64, scale_factor: f32,
) -> touch::Event { ) -> touch::Event {
let id = touch::Finger(touch.id); let id = touch::Finger(touch.id);
let position = { let position = {
let location = touch.location.to_logical::<f64>(scale_factor); let location =
touch.location.to_logical::<f64>(f64::from(scale_factor));
Point::new(location.x as f32, location.y as f32) Point::new(location.x as f32, location.y as f32)
}; };
@ -1180,7 +1204,7 @@ pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {
winit::window::Icon::from_rgba(pixels, size.width, size.height).ok() 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( pub fn ime_purpose(
purpose: input_method::Purpose, purpose: input_method::Purpose,
) -> winit::window::ImePurpose { ) -> winit::window::ImePurpose {

View file

@ -29,9 +29,6 @@ pub use winit;
pub mod clipboard; pub mod clipboard;
pub mod conversion; pub mod conversion;
#[cfg(feature = "system")]
pub mod system;
mod error; mod error;
mod proxy; mod proxy;
mod window; mod window;
@ -53,6 +50,7 @@ use crate::futures::futures::{Future, StreamExt};
use crate::futures::subscription; use crate::futures::subscription;
use crate::futures::{Executor, Runtime}; use crate::futures::{Executor, Runtime};
use crate::graphics::{Compositor, compositor}; use crate::graphics::{Compositor, compositor};
use crate::runtime::system;
use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::user_interface::{self, UserInterface};
use crate::runtime::{Action, Task}; use crate::runtime::{Action, Task};
@ -109,7 +107,7 @@ where
let (_id, open) = runtime::window::open(window_settings); 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 { } else {
task task
}; };
@ -124,6 +122,7 @@ where
let (event_sender, event_receiver) = mpsc::unbounded(); let (event_sender, event_receiver) = mpsc::unbounded();
let (control_sender, control_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>( let instance = Box::pin(run_instance::<P>(
program, program,
@ -134,6 +133,7 @@ where
is_daemon, is_daemon,
graphics_settings, graphics_settings,
settings.fonts, settings.fonts,
system_theme_receiver,
)); ));
let context = task::Context::from_waker(task::noop_waker_ref()); let context = task::Context::from_waker(task::noop_waker_ref());
@ -145,6 +145,7 @@ where
sender: mpsc::UnboundedSender<Event<Action<Message>>>, sender: mpsc::UnboundedSender<Event<Action<Message>>>,
receiver: mpsc::UnboundedReceiver<Control>, receiver: mpsc::UnboundedReceiver<Control>,
error: Option<Error>, error: Option<Error>,
system_theme: Option<oneshot::Sender<theme::Mode>>,
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
canvas: Option<web_sys::HtmlCanvasElement>, canvas: Option<web_sys::HtmlCanvasElement>,
@ -157,6 +158,7 @@ where
sender: event_sender, sender: event_sender,
receiver: control_receiver, receiver: control_receiver,
error: None, error: None,
system_theme: Some(system_theme_sender),
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
canvas: None, canvas: None,
@ -169,10 +171,15 @@ where
where where
F: Future<Output = ()>, F: Future<Output = ()>,
{ {
fn resumed( fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
&mut self, if let Some(sender) = self.system_theme.take() {
_event_loop: &winit::event_loop::ActiveEventLoop, let _ = sender.send(
) { event_loop
.system_theme()
.map(conversion::theme_mode)
.unwrap_or_default(),
);
}
} }
fn new_events( fn new_events(
@ -307,6 +314,7 @@ where
id, id,
settings, settings,
title, title,
scale_factor,
monitor, monitor,
on_open, on_open,
} => { } => {
@ -323,6 +331,7 @@ where
conversion::window_attributes( conversion::window_attributes(
settings, settings,
&title, &title,
scale_factor,
monitor monitor
.or(event_loop.primary_monitor()), .or(event_loop.primary_monitor()),
self.id.clone(), self.id.clone(),
@ -417,7 +426,9 @@ where
); );
} }
Control::Exit => { Control::Exit => {
self.process_event(event_loop, Event::Exit);
event_loop.exit(); event_loop.exit();
break;
} }
Control::Crash(error) => { Control::Crash(error) => {
self.error = Some(error); self.error = Some(error);
@ -464,6 +475,7 @@ enum Event<Message: 'static> {
on_open: oneshot::Sender<window::Id>, on_open: oneshot::Sender<window::Id>,
}, },
EventLoopAwakened(winit::event::Event<Message>), EventLoopAwakened(winit::event::Event<Message>),
Exit,
} }
#[derive(Debug)] #[derive(Debug)]
@ -477,6 +489,7 @@ enum Control {
title: String, title: String,
monitor: Option<winit::monitor::MonitorHandle>, monitor: Option<winit::monitor::MonitorHandle>,
on_open: oneshot::Sender<window::Id>, on_open: oneshot::Sender<window::Id>,
scale_factor: f32,
}, },
} }
@ -489,6 +502,7 @@ async fn run_instance<P>(
is_daemon: bool, is_daemon: bool,
graphics_settings: graphics::Settings, graphics_settings: graphics::Settings,
default_fonts: Vec<Cow<'static, [u8]>>, default_fonts: Vec<Cow<'static, [u8]>>,
mut _system_theme: oneshot::Receiver<theme::Mode>,
) where ) where
P: Program + 'static, P: Program + 'static,
P::Theme: theme::Base, P::Theme: theme::Base,
@ -508,6 +522,38 @@ async fn run_instance<P>(
let mut user_interfaces = ManuallyDrop::new(FxHashMap::default()); let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
let mut clipboard = Clipboard::unconnected(); 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 { loop {
// Empty the queue if possible // Empty the queue if possible
let event = if let Ok(event) = event_receiver.try_next() { let event = if let Ok(event) = event_receiver.try_next() {
@ -586,14 +632,20 @@ async fn run_instance<P>(
} }
} }
debug::theme_changed(|| { let window_theme = window
if window_manager.is_empty() { .theme()
theme::Base::palette(&program.theme(id)) .map(conversion::theme_mode)
} else { .unwrap_or_default();
None
}
});
if system_theme != window_theme {
system_theme = window_theme;
runtime.broadcast(subscription::Event::SystemThemeChanged(
window_theme,
));
}
let is_first = window_manager.is_empty();
let window = window_manager.insert( let window = window_manager.insert(
id, id,
window, window,
@ -602,8 +654,21 @@ async fn run_instance<P>(
.as_mut() .as_mut()
.expect("Compositor must be initialized"), .expect("Compositor must be initialized"),
exit_on_close_request, 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 logical_size = window.state.logical_size();
let _ = user_interfaces.insert( let _ = user_interfaces.insert(
@ -686,6 +751,7 @@ async fn run_instance<P>(
run_action( run_action(
action, action,
&program, &program,
&mut runtime,
&mut compositor, &mut compositor,
&mut events, &mut events,
&mut messages, &mut messages,
@ -695,6 +761,7 @@ async fn run_instance<P>(
&mut window_manager, &mut window_manager,
&mut ui_caches, &mut ui_caches,
&mut is_window_opening, &mut is_window_opening,
&mut system_theme,
); );
actions += 1; actions += 1;
} }
@ -853,11 +920,24 @@ async fn run_instance<P>(
continue; continue;
}; };
if matches!( match window_event {
window_event, winit::event::WindowEvent::Resized(_) => {
winit::event::WindowEvent::Resized(_) window.raw.request_redraw();
) { }
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!( if matches!(
@ -870,6 +950,7 @@ async fn run_instance<P>(
id, id,
)), )),
&program, &program,
&mut runtime,
&mut compositor, &mut compositor,
&mut events, &mut events,
&mut messages, &mut messages,
@ -879,9 +960,14 @@ async fn run_instance<P>(
&mut window_manager, &mut window_manager,
&mut ui_caches, &mut ui_caches,
&mut is_window_opening, &mut is_window_opening,
&mut system_theme,
); );
} else { } else {
window.state.update(&window.raw, &window_event); window.state.update(
&program,
&window.raw,
&window_event,
);
if let Some(event) = conversion::window_event( if let Some(event) = conversion::window_event(
window_event, window_event,
@ -1033,6 +1119,7 @@ async fn run_instance<P>(
_ => {} _ => {}
} }
} }
Event::Exit => break,
} }
} }
@ -1085,6 +1172,7 @@ fn update<P: Program, E: Executor>(
fn run_action<'a, P, C>( fn run_action<'a, P, C>(
action: Action<P::Message>, action: Action<P::Message>,
program: &'a program::Instance<P>, program: &'a program::Instance<P>,
runtime: &mut Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
compositor: &mut Option<C>, compositor: &mut Option<C>,
events: &mut Vec<(window::Id, core::Event)>, events: &mut Vec<(window::Id, core::Event)>,
messages: &mut Vec<P::Message>, messages: &mut Vec<P::Message>,
@ -1097,13 +1185,13 @@ fn run_action<'a, P, C>(
window_manager: &mut WindowManager<P, C>, window_manager: &mut WindowManager<P, C>,
ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>, ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>,
is_window_opening: &mut bool, is_window_opening: &mut bool,
system_theme: &mut theme::Mode,
) where ) where
P: Program, P: Program,
C: Compositor<Renderer = P::Renderer> + 'static, C: Compositor<Renderer = P::Renderer> + 'static,
P::Theme: theme::Base, P::Theme: theme::Base,
{ {
use crate::runtime::clipboard; use crate::runtime::clipboard;
use crate::runtime::system;
use crate::runtime::window; use crate::runtime::window;
match action { match action {
@ -1127,6 +1215,7 @@ fn run_action<'a, P, C>(
id, id,
settings, settings,
title: program.title(id), title: program.title(id),
scale_factor: program.scale_factor(id),
monitor, monitor,
on_open: channel, on_open: channel,
}) })
@ -1187,7 +1276,10 @@ fn run_action<'a, P, C>(
winit::dpi::LogicalSize { winit::dpi::LogicalSize {
width: size.width, width: size.width,
height: size.height, height: size.height,
}, }
.to_physical::<f32>(f64::from(
window.state.scale_factor(),
)),
); );
} }
} }
@ -1198,6 +1290,9 @@ fn run_action<'a, P, C>(
width: size.width, width: size.width,
height: size.height, height: size.height,
} }
.to_physical::<f32>(f64::from(
window.state.scale_factor(),
))
})); }));
} }
} }
@ -1208,6 +1303,9 @@ fn run_action<'a, P, C>(
width: size.width, width: size.width,
height: size.height, height: size.height,
} }
.to_physical::<f32>(f64::from(
window.state.scale_factor(),
))
})); }));
} }
} }
@ -1218,6 +1316,9 @@ fn run_action<'a, P, C>(
width: size.width, width: size.width,
height: size.height, height: size.height,
} }
.to_physical::<f32>(f64::from(
window.state.scale_factor(),
))
})); }));
} }
} }
@ -1231,7 +1332,7 @@ fn run_action<'a, P, C>(
let size = window let size = window
.raw .raw
.inner_size() .inner_size()
.to_logical(window.raw.scale_factor()); .to_logical(f64::from(window.state.scale_factor()));
let _ = channel.send(Size::new(size.width, size.height)); let _ = channel.send(Size::new(size.width, size.height));
} }
@ -1398,21 +1499,44 @@ fn run_action<'a, P, C>(
} }
}, },
Action::System(action) => match action { Action::System(action) => match action {
system::Action::QueryInformation(_channel) => { system::Action::GetInformation(_channel) => {
#[cfg(feature = "system")] #[cfg(feature = "sysinfo")]
{ {
if let Some(compositor) = compositor { if let Some(compositor) = compositor {
let graphics_info = compositor.fetch_information(); let graphics_info = compositor.information();
let _ = std::thread::spawn(move || { let _ = std::thread::spawn(move || {
let information = let information = system_information(graphics_info);
crate::system::information(graphics_info);
let _ = _channel.send(information); 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) => { Action::Widget(operation) => {
let mut current_operation = Some(operation); let mut current_operation = Some(operation);
@ -1521,3 +1645,37 @@ pub fn user_force_quit(
_ => false, _ => 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,
}
}

View file

@ -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,
}
}

View file

@ -55,8 +55,9 @@ where
program: &program::Instance<P>, program: &program::Instance<P>,
compositor: &mut C, compositor: &mut C,
exit_on_close_request: bool, exit_on_close_request: bool,
system_theme: theme::Mode,
) -> &mut Window<P, C> { ) -> &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 viewport_version = state.viewport_version();
let physical_size = state.physical_size(); let physical_size = state.physical_size();
let surface = compositor.create_surface( let surface = compositor.create_surface(

View file

@ -9,18 +9,20 @@ use winit::window::Window;
use std::fmt::{Debug, Formatter}; 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> pub struct State<P: Program>
where where
P::Theme: theme::Base, P::Theme: theme::Base,
{ {
title: String, title: String,
scale_factor: f64, scale_factor: f32,
viewport: Viewport, viewport: Viewport,
viewport_version: u64, viewport_version: u64,
cursor_position: Option<winit::dpi::PhysicalPosition<f64>>, cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
modifiers: winit::keyboard::ModifiersState, modifiers: winit::keyboard::ModifiersState,
theme: P::Theme, theme: Option<P::Theme>,
theme_mode: theme::Mode,
default_theme: P::Theme,
style: theme::Style, style: theme::Style,
} }
@ -29,7 +31,7 @@ where
P::Theme: theme::Base, P::Theme: theme::Base,
{ {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 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("title", &self.title)
.field("scale_factor", &self.scale_factor) .field("scale_factor", &self.scale_factor)
.field("viewport", &self.viewport) .field("viewport", &self.viewport)
@ -49,18 +51,22 @@ where
program: &program::Instance<P>, program: &program::Instance<P>,
window_id: window::Id, window_id: window::Id,
window: &Window, window: &Window,
system_theme: theme::Mode,
) -> Self { ) -> Self {
let title = program.title(window_id); let title = program.title(window_id);
let scale_factor = program.scale_factor(window_id); let scale_factor = program.scale_factor(window_id);
let theme = program.theme(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 viewport = {
let physical_size = window.inner_size(); let physical_size = window.inner_size();
Viewport::with_physical_size( Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height), Size::new(physical_size.width, physical_size.height),
window.scale_factor() * scale_factor, window.scale_factor() as f32 * scale_factor,
) )
}; };
@ -72,6 +78,8 @@ where
cursor_position: None, cursor_position: None,
modifiers: winit::keyboard::ModifiersState::default(), modifiers: winit::keyboard::ModifiersState::default(),
theme, theme,
theme_mode,
default_theme,
style, style,
} }
} }
@ -99,7 +107,7 @@ where
} }
/// Returns the current scale factor of the [`Viewport`] of the [`State`]. /// Returns the current scale factor of the [`Viewport`] of the [`State`].
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f32 {
self.viewport.scale_factor() self.viewport.scale_factor()
} }
@ -123,7 +131,12 @@ where
/// Returns the current theme of the [`State`]. /// Returns the current theme of the [`State`].
pub fn theme(&self) -> &P::Theme { 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`]. /// Returns the current background [`Color`] of the [`State`].
@ -137,14 +150,19 @@ where
} }
/// Processes the provided window event and updates the [`State`] accordingly. /// 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 { match event {
WindowEvent::Resized(new_size) => { WindowEvent::Resized(new_size) => {
let size = Size::new(new_size.width, new_size.height); let size = Size::new(new_size.width, new_size.height);
self.viewport = Viewport::with_physical_size( self.viewport = Viewport::with_physical_size(
size, size,
window.scale_factor() * self.scale_factor, window.scale_factor() as f32 * self.scale_factor,
); );
self.viewport_version = self.viewport_version.wrapping_add(1); self.viewport_version = self.viewport_version.wrapping_add(1);
@ -157,7 +175,7 @@ where
self.viewport = Viewport::with_physical_size( self.viewport = Viewport::with_physical_size(
size, size,
new_scale_factor * self.scale_factor, *new_scale_factor as f32 * self.scale_factor,
); );
self.viewport_version = self.viewport_version.wrapping_add(1); self.viewport_version = self.viewport_version.wrapping_add(1);
@ -174,6 +192,16 @@ where
WindowEvent::ModifiersChanged(new_modifiers) => { WindowEvent::ModifiersChanged(new_modifiers) => {
self.modifiers = new_modifiers.state(); 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. /// window.
/// ///
/// Normally, a [`Program`] should be synchronized with its [`State`] /// Normally, a [`Program`] should be synchronized with its [`State`]
/// and window after calling [`State::update`]. /// and window after calling [`Program::update`].
pub fn synchronize( pub fn synchronize(
&mut self, &mut self,
program: &program::Instance<P>, program: &program::Instance<P>,
@ -208,7 +236,7 @@ where
{ {
self.viewport = Viewport::with_physical_size( self.viewport = Viewport::with_physical_size(
Size::new(new_size.width, new_size.height), Size::new(new_size.width, new_size.height),
window.scale_factor() * new_scale_factor, window.scale_factor() as f32 * new_scale_factor,
); );
self.viewport_version = self.viewport_version.wrapping_add(1); self.viewport_version = self.viewport_version.wrapping_add(1);
@ -217,6 +245,45 @@ where
// Update theme and appearance // Update theme and appearance
self.theme = program.theme(window_id); 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;
}
} }
} }