Merge pull request #3051 from iced-rs/replace-dark-light
System Theme Reactions
This commit is contained in:
commit
a9091f9edd
45 changed files with 1069 additions and 594 deletions
734
Cargo.lock
generated
734
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
18
Cargo.toml
18
Cargo.toml
|
|
@ -22,7 +22,7 @@ all-features = true
|
||||||
maintenance = { status = "actively-developed" }
|
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
|
||||||
|
|
@ -54,21 +54,19 @@ 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 advanced module
|
# Enables the advanced module
|
||||||
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
|
|
||||||
auto-detect-theme = ["iced_core/auto-detect-theme"]
|
|
||||||
# Enables basic text shaping by default
|
# Enables basic text shaping by default
|
||||||
basic-shaping = ["iced_core/basic-shaping"]
|
basic-shaping = ["iced_core/basic-shaping"]
|
||||||
# Enables advanced text shaping by default
|
# Enables advanced text shaping by default
|
||||||
|
|
@ -79,6 +77,8 @@ strict-assertions = ["iced_renderer/strict-assertions"]
|
||||||
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
|
||||||
|
|
@ -175,10 +175,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 }
|
||||||
|
|
@ -188,16 +187,17 @@ 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 }
|
||||||
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"
|
||||||
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"
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ keywords.workspace = true
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
auto-detect-theme = ["dep:dark-light"]
|
|
||||||
advanced = []
|
advanced = []
|
||||||
crisp = []
|
crisp = []
|
||||||
basic-shaping = []
|
basic-shaping = []
|
||||||
|
|
@ -32,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"]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,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::{Alignment::Center, Color, Element, Length::Fill};
|
use crate::core::{Alignment::Center, Color, Element, Length::Fill};
|
||||||
|
|
@ -90,7 +90,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,14 +311,12 @@ where
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let theme = program.theme(state, window);
|
fn derive_theme<T: theme::Base>(theme: &T) -> Theme {
|
||||||
|
|
||||||
let derive_theme = move || {
|
|
||||||
theme
|
theme
|
||||||
.palette()
|
.palette()
|
||||||
.map(|palette| Theme::custom("iced devtools", palette))
|
.map(|palette| Theme::custom("iced devtools", palette))
|
||||||
.unwrap_or_default()
|
.unwrap_or(Theme::Dark)
|
||||||
};
|
}
|
||||||
|
|
||||||
let mode = match &self.mode {
|
let mode = match &self.mode {
|
||||||
Mode::None => None,
|
Mode::None => None,
|
||||||
|
|
@ -340,7 +342,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map(|mode| {
|
.map(|mode| {
|
||||||
themer(derive_theme(), Element::from(mode).map(Event::Message))
|
themer(derive_theme, Element::from(mode).map(Event::Message))
|
||||||
});
|
});
|
||||||
|
|
||||||
let notification = self
|
let notification = self
|
||||||
|
|
@ -359,7 +361,7 @@ where
|
||||||
.push_maybe(mode.map(opaque))
|
.push_maybe(mode.map(opaque))
|
||||||
.push_maybe(notification.map(|notification| {
|
.push_maybe(notification.map(|notification| {
|
||||||
themer(
|
themer(
|
||||||
derive_theme(),
|
derive_theme,
|
||||||
bottom_right(opaque(
|
bottom_right(opaque(
|
||||||
container(notification)
|
container(notification)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
|
|
@ -389,7 +391,7 @@ where
|
||||||
Subscription::batch([subscription, hotkeys, commands])
|
Subscription::batch([subscription, hotkeys, commands])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self, program: &P, window: window::Id) -> P::Theme {
|
fn theme(&self, program: &P, window: window::Id) -> Option<P::Theme> {
|
||||||
program.theme(self.state(), window)
|
program.theme(self.state(), window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -116,7 +117,7 @@ impl Layout {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Option<Theme> {
|
||||||
self.theme.clone()
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -138,12 +138,8 @@ 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) -> f32 {
|
fn scale_factor(&self, window: window::Id) -> f32 {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)?;
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
99f418007af163f172e163565f166da31015521e1bf7de95fa55cda2fb5a7db5
|
0acb67235c6a11014a2d2b825e0a70069bca0c67bee0cdb38a0144fc72b25220
|
||||||
|
|
@ -5,7 +5,7 @@ use iced::widget::{
|
||||||
};
|
};
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced::{
|
use iced::{
|
||||||
Center, Element, Fill, Font, Function, Subscription, Task as Command,
|
Center, Element, Fill, Font, Function, Subscription, Task as Command, Theme,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -151,7 +151,7 @@ impl Todos {
|
||||||
widget::focus_next()
|
widget::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(),
|
||||||
};
|
};
|
||||||
|
|
@ -194,7 +194,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)
|
||||||
|
|
@ -447,7 +447,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()
|
||||||
|
|
@ -471,6 +471,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 {
|
||||||
|
|
|
||||||
|
|
@ -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!",
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ pub trait Program: Sized {
|
||||||
type Message: Message + 'static;
|
type Message: Message + '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;
|
||||||
|
|
@ -86,8 +86,12 @@ 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 {
|
||||||
|
|
@ -152,7 +156,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,7 +242,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,7 +268,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,
|
||||||
|
|
@ -273,7 +277,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;
|
||||||
|
|
@ -285,7 +289,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -407,7 +411,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -478,7 +482,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -561,7 +565,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -628,7 +632,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -313,8 +313,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(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
//! }
|
//! }
|
||||||
|
|
@ -35,7 +35,7 @@ use crate::shell;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
use crate::window;
|
use crate::window;
|
||||||
use crate::{
|
use crate::{
|
||||||
Element, Executor, Font, Result, Settings, Size, Subscription, Task,
|
Element, Executor, Font, Result, Settings, Size, Subscription, Task, Theme,
|
||||||
};
|
};
|
||||||
|
|
||||||
use iced_debug as debug;
|
use iced_debug as debug;
|
||||||
|
|
@ -75,14 +75,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: program::Message + 'static,
|
Message: program::Message + 'static,
|
||||||
Theme: Default + theme::Base,
|
Theme: theme::Base,
|
||||||
Renderer: program::Renderer,
|
Renderer: program::Renderer,
|
||||||
{
|
{
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
@ -101,11 +101,11 @@ where
|
||||||
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||||
where
|
where
|
||||||
Message: program::Message + 'static,
|
Message: program::Message + '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;
|
||||||
|
|
@ -320,10 +320,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>,
|
||||||
> {
|
> {
|
||||||
|
|
@ -355,13 +355,13 @@ 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| {
|
raw: program::with_theme(self.raw, move |state, _window| {
|
||||||
debug::hot(|| f(state))
|
debug::hot(|| f.theme(state))
|
||||||
}),
|
}),
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
window: self.window,
|
window: self.window,
|
||||||
|
|
@ -425,12 +425,12 @@ impl<P: Program> 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>,
|
||||||
|
|
@ -464,18 +464,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,
|
||||||
{
|
{
|
||||||
|
|
@ -488,18 +488,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>>,
|
||||||
|
|
@ -513,13 +513,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,
|
||||||
|
|
@ -529,3 +529,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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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: program::Message + 'static,
|
Message: program::Message + '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: program::Message + 'static,
|
Message: program::Message + '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);
|
||||||
|
|
@ -148,9 +148,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,
|
||||||
|
|
@ -160,7 +160,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,
|
||||||
|
|
@ -170,7 +170,7 @@ impl<State, Message> Update<State, Message> for () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, State, Message, C> Update<State, Message> for T
|
impl<T, State, Message, C> UpdateFn<State, Message> for T
|
||||||
where
|
where
|
||||||
T: Fn(&mut State, Message, Instant) -> C,
|
T: Fn(&mut State, Message, Instant) -> C,
|
||||||
C: Into<Task<Message>>,
|
C: Into<Task<Message>>,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ use crate::program::{self, Program};
|
||||||
use crate::shell;
|
use crate::shell;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
use crate::window;
|
use crate::window;
|
||||||
use crate::{Element, Executor, Font, Result, Settings, Subscription, Task};
|
use crate::{
|
||||||
|
Element, Executor, Font, Result, Settings, Subscription, Task, Theme,
|
||||||
|
};
|
||||||
|
|
||||||
use iced_debug as debug;
|
use iced_debug as debug;
|
||||||
|
|
||||||
|
|
@ -21,14 +23,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: program::Message + 'static,
|
Message: program::Message + 'static,
|
||||||
Theme: Default + theme::Base,
|
Theme: theme::Base,
|
||||||
Renderer: program::Renderer,
|
Renderer: program::Renderer,
|
||||||
{
|
{
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
@ -47,11 +49,11 @@ where
|
||||||
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||||
where
|
where
|
||||||
Message: program::Message + 'static,
|
Message: program::Message + '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;
|
||||||
|
|
@ -169,10 +171,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>,
|
||||||
> {
|
> {
|
||||||
|
|
@ -202,13 +204,13 @@ 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, move |state, window| {
|
raw: program::with_theme(self.raw, move |state, window| {
|
||||||
debug::hot(|| f(state, window))
|
debug::hot(|| f.theme(state, window))
|
||||||
}),
|
}),
|
||||||
settings: self.settings,
|
settings: self.settings,
|
||||||
}
|
}
|
||||||
|
|
@ -266,18 +268,18 @@ impl<P: Program> 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,
|
||||||
{
|
{
|
||||||
|
|
@ -290,7 +292,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,
|
||||||
|
|
@ -300,7 +302,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,
|
||||||
|
|
@ -314,3 +316,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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
13
src/lib.rs
13
src/lib.rs
|
|
@ -587,11 +587,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: program::Message + 'static,
|
Message: program::Message + '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()
|
||||||
|
|
|
||||||
|
|
@ -494,10 +494,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()])
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -2063,7 +2063,7 @@ where
|
||||||
|
|
||||||
/// A widget that applies any `Theme` to its contents.
|
/// A widget that applies any `Theme` to its contents.
|
||||||
pub fn themer<'a, Message, OldTheme, NewTheme, Renderer>(
|
pub fn themer<'a, Message, OldTheme, NewTheme, Renderer>(
|
||||||
new_theme: NewTheme,
|
to_theme: impl Fn(&OldTheme) -> NewTheme,
|
||||||
content: impl Into<Element<'a, Message, NewTheme, Renderer>>,
|
content: impl Into<Element<'a, Message, NewTheme, Renderer>>,
|
||||||
) -> Themer<
|
) -> Themer<
|
||||||
'a,
|
'a,
|
||||||
|
|
@ -2077,7 +2077,7 @@ where
|
||||||
Renderer: core::Renderer,
|
Renderer: core::Renderer,
|
||||||
NewTheme: Clone,
|
NewTheme: Clone,
|
||||||
{
|
{
|
||||||
Themer::new(move |_| new_theme.clone(), content)
|
Themer::new(to_theme, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [`PaneGrid`] with the given [`pane_grid::State`] and view function.
|
/// Creates a [`PaneGrid`] with the given [`pane_grid::State`] and view function.
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,14 @@ 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"]
|
||||||
program = []
|
program = []
|
||||||
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
|
||||||
|
|
@ -42,3 +43,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
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
@ -322,7 +323,7 @@ pub fn window_event(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a [`window::Level`] to a [`winit`] window level.
|
/// Converts a [`window::Level`] into a [`winit`] window level.
|
||||||
///
|
///
|
||||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
/// [`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 {
|
||||||
|
|
@ -335,7 +336,7 @@ pub fn window_level(level: window::Level) -> winit::window::WindowLevel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a [`window::Position`] to a [`winit`] logical position for a given monitor.
|
/// Converts a [`window::Position`] into a [`winit`] logical position for a given monitor.
|
||||||
///
|
///
|
||||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||||
pub fn position(
|
pub fn position(
|
||||||
|
|
@ -407,7 +408,7 @@ pub fn position(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a [`window::Mode`] to a [`winit`] fullscreen mode.
|
/// Converts a [`window::Mode`] into a [`winit`] fullscreen mode.
|
||||||
///
|
///
|
||||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||||
pub fn fullscreen(
|
pub fn fullscreen(
|
||||||
|
|
@ -422,7 +423,7 @@ pub fn fullscreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a [`window::Mode`] to a visibility flag.
|
/// Converts a [`window::Mode`] into a visibility flag.
|
||||||
pub fn visible(mode: window::Mode) -> bool {
|
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,
|
||||||
|
|
@ -430,7 +431,7 @@ pub fn visible(mode: window::Mode) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a [`winit`] fullscreen mode to a [`window::Mode`].
|
/// Converts a [`winit`] fullscreen mode into a [`window::Mode`].
|
||||||
///
|
///
|
||||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
/// [`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 {
|
||||||
|
|
@ -440,7 +441,28 @@ pub fn mode(mode: Option<winit::window::Fullscreen>) -> window::Mode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a [`mouse::Interaction`] to a [`winit`] cursor icon.
|
/// Converts a [`winit`] window theme into a [`theme::Mode`].
|
||||||
|
///
|
||||||
|
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||||
|
pub fn theme_mode(theme: winit::window::Theme) -> theme::Mode {
|
||||||
|
match theme {
|
||||||
|
winit::window::Theme::Light => theme::Mode::Light,
|
||||||
|
winit::window::Theme::Dark => theme::Mode::Dark,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a [`theme::Mode`] into a window theme.
|
||||||
|
///
|
||||||
|
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||||
|
pub fn window_theme(mode: theme::Mode) -> Option<winit::window::Theme> {
|
||||||
|
match mode {
|
||||||
|
theme::Mode::None => None,
|
||||||
|
theme::Mode::Light => Some(winit::window::Theme::Light),
|
||||||
|
theme::Mode::Dark => Some(winit::window::Theme::Dark),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a [`mouse::Interaction`] into a [`winit`] cursor icon.
|
||||||
///
|
///
|
||||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||||
pub fn mouse_interaction(
|
pub fn mouse_interaction(
|
||||||
|
|
@ -511,7 +533,7 @@ 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: f32,
|
scale_factor: f32,
|
||||||
|
|
@ -1182,7 +1204,7 @@ pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {
|
||||||
winit::window::Icon::from_rgba(pixels, size.width, size.height).ok()
|
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 {
|
||||||
|
|
|
||||||
181
winit/src/lib.rs
181
winit/src/lib.rs
|
|
@ -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};
|
||||||
|
|
||||||
|
|
@ -111,7 +109,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
|
||||||
};
|
};
|
||||||
|
|
@ -126,6 +124,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,
|
||||||
|
|
@ -136,6 +135,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());
|
||||||
|
|
@ -147,6 +147,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>,
|
||||||
|
|
@ -159,6 +160,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,
|
||||||
|
|
@ -172,10 +174,15 @@ where
|
||||||
Message: std::fmt::Debug,
|
Message: std::fmt::Debug,
|
||||||
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(
|
||||||
|
|
@ -498,6 +505,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,
|
||||||
|
|
@ -517,6 +525,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() {
|
||||||
|
|
@ -595,14 +635,7 @@ async fn run_instance<P>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug::theme_changed(|| {
|
let is_first = window_manager.is_empty();
|
||||||
if window_manager.is_empty() {
|
|
||||||
theme::Base::palette(&program.theme(id))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let window = window_manager.insert(
|
let window = window_manager.insert(
|
||||||
id,
|
id,
|
||||||
window,
|
window,
|
||||||
|
|
@ -611,8 +644,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(
|
||||||
|
|
@ -695,6 +741,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,
|
||||||
|
|
@ -704,6 +751,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;
|
||||||
}
|
}
|
||||||
|
|
@ -862,11 +910,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!(
|
||||||
|
|
@ -879,6 +940,7 @@ async fn run_instance<P>(
|
||||||
id,
|
id,
|
||||||
)),
|
)),
|
||||||
&program,
|
&program,
|
||||||
|
&mut runtime,
|
||||||
&mut compositor,
|
&mut compositor,
|
||||||
&mut events,
|
&mut events,
|
||||||
&mut messages,
|
&mut messages,
|
||||||
|
|
@ -888,9 +950,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,
|
||||||
|
|
@ -1095,6 +1162,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>,
|
||||||
|
|
@ -1107,13 +1175,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 {
|
||||||
|
|
@ -1421,21 +1489,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);
|
||||||
|
|
@ -1544,3 +1635,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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
//! Access the native system.
|
|
||||||
use crate::graphics::compositor;
|
|
||||||
use crate::runtime::system::{Action, Information};
|
|
||||||
use crate::runtime::{self, Task};
|
|
||||||
|
|
||||||
/// Query for available system information.
|
|
||||||
pub fn fetch_information() -> Task<Information> {
|
|
||||||
runtime::task::oneshot(|channel| {
|
|
||||||
runtime::Action::System(Action::QueryInformation(channel))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn information(
|
|
||||||
graphics_info: compositor::Information,
|
|
||||||
) -> Information {
|
|
||||||
use sysinfo::{Process, System};
|
|
||||||
let mut system = System::new_all();
|
|
||||||
system.refresh_all();
|
|
||||||
|
|
||||||
let cpu_brand = system
|
|
||||||
.cpus()
|
|
||||||
.first()
|
|
||||||
.map(|cpu| cpu.brand().to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let memory_used = sysinfo::get_current_pid()
|
|
||||||
.and_then(|pid| system.process(pid).ok_or("Process not found"))
|
|
||||||
.map(Process::memory)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Information {
|
|
||||||
system_name: System::name(),
|
|
||||||
system_kernel: System::kernel_version(),
|
|
||||||
system_version: System::long_os_version(),
|
|
||||||
system_short_version: System::os_version(),
|
|
||||||
cpu_brand,
|
|
||||||
cpu_cores: system.physical_core_count(),
|
|
||||||
memory_total: system.total_memory(),
|
|
||||||
memory_used,
|
|
||||||
graphics_adapter: graphics_info.adapter,
|
|
||||||
graphics_backend: graphics_info.backend,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -55,8 +55,9 @@ where
|
||||||
program: &program::Instance<P>,
|
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(
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ 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,
|
||||||
|
|
@ -20,7 +20,9 @@ where
|
||||||
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,11 +51,15 @@ 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();
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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,7 +150,12 @@ 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);
|
||||||
|
|
@ -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>,
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue