Merge branch 'master' into feature/test-recorder
This commit is contained in:
commit
a052ce58b0
69 changed files with 1555 additions and 833 deletions
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
|
|
@ -26,4 +26,3 @@ jobs:
|
|||
cargo test --verbose --workspace
|
||||
cargo test --verbose --workspace -- --ignored
|
||||
cargo test --verbose --workspace --all-features
|
||||
cargo test --verbose --workspace --all-features -- --ignored
|
||||
|
|
|
|||
789
Cargo.lock
generated
789
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
22
Cargo.toml
22
Cargo.toml
|
|
@ -22,7 +22,7 @@ all-features = true
|
|||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[features]
|
||||
default = ["wgpu", "tiny-skia", "crisp", "web-colors", "auto-detect-theme", "thread-pool"]
|
||||
default = ["wgpu", "tiny-skia", "crisp", "web-colors", "thread-pool", "linux-theme-detection"]
|
||||
# Enables the `wgpu` GPU-accelerated renderer backend
|
||||
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
|
||||
# Enables the `tiny-skia` software renderer backend
|
||||
|
|
@ -56,14 +56,14 @@ tokio = ["iced_futures/tokio"]
|
|||
# Enables `smol` as the `executor::Default` on native platforms
|
||||
smol = ["iced_futures/smol"]
|
||||
# Enables querying system information
|
||||
system = ["iced_winit/system"]
|
||||
sysinfo = ["iced_winit/sysinfo"]
|
||||
# Enables broken "sRGB linear" blending to reproduce color management of the Web
|
||||
web-colors = ["iced_renderer/web-colors"]
|
||||
# Enables pixel snapping for crisp edges by default (can cause jitter!)
|
||||
crisp = ["iced_core/crisp", "iced_widget/crisp"]
|
||||
# Enables the WebGL backend
|
||||
webgl = ["iced_renderer/webgl"]
|
||||
# Enables syntax highligthing
|
||||
# Enables syntax highlighting
|
||||
highlighter = ["iced_highlighter", "iced_widget/highlighter"]
|
||||
# Enables the `widget::selector` module
|
||||
selector = ["iced_runtime/selector"]
|
||||
|
|
@ -71,14 +71,18 @@ selector = ["iced_runtime/selector"]
|
|||
advanced = ["iced_core/advanced", "iced_widget/advanced"]
|
||||
# Embeds Fira Sans into the final application; useful for testing and Wasm builds
|
||||
fira-sans = ["iced_renderer/fira-sans"]
|
||||
# Auto-detects light/dark mode for the built-in theme
|
||||
auto-detect-theme = ["iced_core/auto-detect-theme"]
|
||||
# Enables basic text shaping by default
|
||||
basic-shaping = ["iced_core/basic-shaping"]
|
||||
# Enables advanced text shaping by default
|
||||
advanced-shaping = ["iced_core/advanced-shaping"]
|
||||
# Enables strict assertions for debugging purposes at the expense of performance
|
||||
strict-assertions = ["iced_renderer/strict-assertions"]
|
||||
# Redraws on every runtime event, and not only when a widget requests it
|
||||
unconditional-rendering = ["iced_winit/unconditional-rendering"]
|
||||
# Enables support for the `sipper` library
|
||||
sipper = ["iced_runtime/sipper"]
|
||||
# Enables Linux system theme detection
|
||||
linux-theme-detection = ["iced_winit/linux-theme-detection"]
|
||||
|
||||
[dependencies]
|
||||
iced_debug.workspace = true
|
||||
|
|
@ -181,10 +185,9 @@ bytemuck = { version = "1.0", features = ["derive"] }
|
|||
bytes = "1.6"
|
||||
cargo-hot = { package = "cargo-hot-protocol", git = "https://github.com/hecrj/cargo-hot.git", rev = "b8dc518b8640928178a501257e353b73bc06cf47" }
|
||||
cosmic-text = "0.14"
|
||||
dark-light = "2.0"
|
||||
cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "453cedec0d2ec563bd7fa87e84a2319bcebb1ba3" }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
glam = "0.25"
|
||||
cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "453cedec0d2ec563bd7fa87e84a2319bcebb1ba3" }
|
||||
guillotiere = "0.6"
|
||||
half = "2.2"
|
||||
image = { version = "0.25", default-features = false }
|
||||
|
|
@ -194,18 +197,19 @@ lilt = "0.8"
|
|||
log = "0.4"
|
||||
lyon = "1.0"
|
||||
lyon_path = "1.0"
|
||||
mundy = { version = "0.2", default-features = false }
|
||||
nom = "8"
|
||||
num-traits = "0.2"
|
||||
ouroboros = "0.18"
|
||||
png = "0.17"
|
||||
png = "0.18"
|
||||
pulldown-cmark = "0.12"
|
||||
qrcode = { version = "0.13", default-features = false }
|
||||
raw-window-handle = "0.6"
|
||||
resvg = "0.42"
|
||||
rfd = "0.15"
|
||||
rustc-hash = "2.0"
|
||||
serde = "1.0"
|
||||
semver = "1.0"
|
||||
serde = "1.0"
|
||||
sha2 = "0.10"
|
||||
sipper = "0.1"
|
||||
smol = "2"
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ keywords.workspace = true
|
|||
workspace = true
|
||||
|
||||
[features]
|
||||
auto-detect-theme = ["dep:dark-light"]
|
||||
advanced = []
|
||||
crisp = []
|
||||
basic-shaping = []
|
||||
advanced-shaping = []
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
|
|
@ -30,9 +31,6 @@ smol_str.workspace = true
|
|||
thiserror.workspace = true
|
||||
web-time.workspace = true
|
||||
|
||||
dark-light.workspace = true
|
||||
dark-light.optional = true
|
||||
|
||||
serde.workspace = true
|
||||
serde.optional = true
|
||||
serde.features = ["derive"]
|
||||
|
|
|
|||
|
|
@ -78,14 +78,19 @@ where
|
|||
let total_spacing = spacing * items.len().saturating_sub(1) as f32;
|
||||
let max_cross = axis.cross(limits.max());
|
||||
|
||||
let (main_compress, cross_compress) = {
|
||||
let compression = limits.compression();
|
||||
let (main_compress, cross_compress) =
|
||||
axis.pack(compression.width, compression.height);
|
||||
axis.pack(compression.width, compression.height)
|
||||
};
|
||||
|
||||
let compression = {
|
||||
let (compress_x, compress_y) = axis.pack(main_compress, false);
|
||||
Size::new(compress_x, compress_y)
|
||||
};
|
||||
|
||||
let mut fill_main_sum = 0;
|
||||
let mut some_fill_cross = false;
|
||||
let mut cross = if cross_compress { 0.0 } else { max_cross };
|
||||
|
||||
let mut available = axis.main(limits.max()) - total_spacing;
|
||||
|
||||
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
||||
|
|
|
|||
|
|
@ -130,8 +130,17 @@ impl From<Alignment> for alignment::Horizontal {
|
|||
}
|
||||
|
||||
/// The shaping strategy of some text.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Shaping {
|
||||
/// Auto-detect the best shaping strategy from the text.
|
||||
///
|
||||
/// This strategy will use [`Basic`](Self::Basic) shaping if the
|
||||
/// text consists of only ASCII characters; otherwise, it will
|
||||
/// use [`Advanced`](Self::Advanced) shaping.
|
||||
///
|
||||
/// This is the default, if neither the `basic-shaping` nor `advanced-shaping`
|
||||
/// features are enabled.
|
||||
Auto,
|
||||
/// No shaping and no font fallback.
|
||||
///
|
||||
/// This shaping strategy is very cheap, but it will not display complex
|
||||
|
|
@ -140,8 +149,8 @@ pub enum Shaping {
|
|||
/// You should use this strategy when you have complete control of the text
|
||||
/// and the font you are displaying in your application.
|
||||
///
|
||||
/// This is the default.
|
||||
#[default]
|
||||
/// This will be the default if the `basic-shaping` feature is enabled and
|
||||
/// the `advanced-shaping` feature is disabled.
|
||||
Basic,
|
||||
/// Advanced text shaping and font fallback.
|
||||
///
|
||||
|
|
@ -150,9 +159,23 @@ pub enum Shaping {
|
|||
/// may be needed to display all of the glyphs.
|
||||
///
|
||||
/// Advanced shaping is expensive! You should only enable it when necessary.
|
||||
///
|
||||
/// This will be the default if the `advanced-shaping` feature is enabled.
|
||||
Advanced,
|
||||
}
|
||||
|
||||
impl Default for Shaping {
|
||||
fn default() -> Self {
|
||||
if cfg!(feature = "advanced-shaping") {
|
||||
Self::Advanced
|
||||
} else if cfg!(feature = "basic-shaping") {
|
||||
Self::Basic
|
||||
} else {
|
||||
Self::Auto
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The wrapping strategy of some text.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub enum Wrapping {
|
||||
|
|
|
|||
|
|
@ -166,31 +166,6 @@ impl Theme {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
#[cfg(feature = "auto-detect-theme")]
|
||||
{
|
||||
use std::sync::LazyLock;
|
||||
|
||||
static DEFAULT: LazyLock<Theme> = LazyLock::new(|| {
|
||||
match dark_light::detect()
|
||||
.unwrap_or(dark_light::Mode::Unspecified)
|
||||
{
|
||||
dark_light::Mode::Dark => Theme::Dark,
|
||||
dark_light::Mode::Light | dark_light::Mode::Unspecified => {
|
||||
Theme::Light
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
DEFAULT.clone()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "auto-detect-theme"))]
|
||||
Theme::Light
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Theme {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
|
|
@ -256,6 +231,18 @@ impl fmt::Display for Custom {
|
|||
}
|
||||
}
|
||||
|
||||
/// A theme mode, denoting the tone or brightness of a theme.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum Mode {
|
||||
/// No specific tone.
|
||||
#[default]
|
||||
None,
|
||||
/// A mode referring to themes with light tones.
|
||||
Light,
|
||||
/// A mode referring to themes with dark tones.
|
||||
Dark,
|
||||
}
|
||||
|
||||
/// The base style of a theme.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Style {
|
||||
|
|
@ -268,7 +255,13 @@ pub struct Style {
|
|||
|
||||
/// The default blank style of a theme.
|
||||
pub trait Base {
|
||||
/// Returns the default base [`Style`] of a theme.
|
||||
/// Returns the default theme for the preferred [`Mode`].
|
||||
fn default(preference: Mode) -> Self;
|
||||
|
||||
/// Returns the [`Mode`] of the theme.
|
||||
fn mode(&self) -> Mode;
|
||||
|
||||
/// Returns the default base [`Style`] of the theme.
|
||||
fn base(&self) -> Style;
|
||||
|
||||
/// Returns the color [`Palette`] of the theme.
|
||||
|
|
@ -280,6 +273,39 @@ pub trait Base {
|
|||
}
|
||||
|
||||
impl Base for Theme {
|
||||
fn default(preference: Mode) -> Self {
|
||||
use std::env;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static SYSTEM: OnceLock<Option<Theme>> = OnceLock::new();
|
||||
|
||||
let system = SYSTEM.get_or_init(|| {
|
||||
let name = env::var("ICED_THEME").ok()?;
|
||||
|
||||
Theme::ALL
|
||||
.iter()
|
||||
.find(|theme| theme.to_string() == name)
|
||||
.cloned()
|
||||
});
|
||||
|
||||
if let Some(system) = system {
|
||||
return system.clone();
|
||||
}
|
||||
|
||||
match preference {
|
||||
Mode::None | Mode::Light => Self::Light,
|
||||
Mode::Dark => Self::Dark,
|
||||
}
|
||||
}
|
||||
|
||||
fn mode(&self) -> Mode {
|
||||
if self.extended_palette().is_dark {
|
||||
Mode::Dark
|
||||
} else {
|
||||
Mode::Light
|
||||
}
|
||||
}
|
||||
|
||||
fn base(&self) -> Style {
|
||||
default(self)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ pub struct Screenshot {
|
|||
pub size: Size<u32>,
|
||||
/// The scale factor of the [`Screenshot`]. This can be useful when converting between widget
|
||||
/// bounds (which are in logical pixels) to crop screenshots.
|
||||
pub scale_factor: f64,
|
||||
pub scale_factor: f32,
|
||||
}
|
||||
|
||||
impl Debug for Screenshot {
|
||||
|
|
@ -35,7 +35,7 @@ impl Screenshot {
|
|||
pub fn new(
|
||||
bytes: impl Into<Bytes>,
|
||||
size: Size<u32>,
|
||||
scale_factor: f64,
|
||||
scale_factor: f32,
|
||||
) -> Self {
|
||||
Self {
|
||||
bytes: bytes.into(),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ mod time_machine;
|
|||
|
||||
use crate::core::border;
|
||||
use crate::core::keyboard;
|
||||
use crate::core::theme::{self, Base, Theme};
|
||||
use crate::core::theme::{self, Theme};
|
||||
use crate::core::time::seconds;
|
||||
use crate::core::window;
|
||||
use crate::core::{
|
||||
|
|
@ -98,7 +98,11 @@ where
|
|||
state.subscription(&self.program)
|
||||
}
|
||||
|
||||
fn theme(&self, state: &Self::State, window: window::Id) -> Self::Theme {
|
||||
fn theme(
|
||||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Option<Self::Theme> {
|
||||
state.theme(&self.program, window)
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +110,7 @@ where
|
|||
state.style(&self.program, theme)
|
||||
}
|
||||
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
|
||||
state.scale_factor(&self.program, window)
|
||||
}
|
||||
}
|
||||
|
|
@ -161,11 +165,11 @@ where
|
|||
show_notification: true,
|
||||
time_machine: TimeMachine::new(),
|
||||
},
|
||||
task::blocking(|mut sender| {
|
||||
Task::batch([task::blocking(|mut sender| {
|
||||
thread::sleep(seconds(2));
|
||||
let _ = sender.try_send(());
|
||||
})
|
||||
.map(|_| Message::HideNotification),
|
||||
.map(|_| Message::HideNotification)]),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -306,10 +310,7 @@ where
|
|||
let state = self.state();
|
||||
|
||||
let view = {
|
||||
let theme = program.theme(state, window);
|
||||
|
||||
let view: Element<'_, _, Theme, _> =
|
||||
themer(theme, program.view(state, window)).into();
|
||||
let view = program.view(state, window);
|
||||
|
||||
if self.time_machine.is_rewinding() {
|
||||
view.map(|_| Event::Discard)
|
||||
|
|
@ -318,11 +319,13 @@ where
|
|||
}
|
||||
};
|
||||
|
||||
let theme = program
|
||||
let theme = || {
|
||||
program
|
||||
.theme(state, window)
|
||||
.palette()
|
||||
.as_ref()
|
||||
.and_then(theme::Base::palette)
|
||||
.map(|palette| Theme::custom("iced devtools", palette))
|
||||
.unwrap_or_default();
|
||||
};
|
||||
|
||||
let setup = if let Mode::Setup(setup) = &self.mode {
|
||||
let stage: Element<'_, _, Theme, P::Renderer> = match setup {
|
||||
|
|
@ -346,7 +349,7 @@ where
|
|||
} else {
|
||||
None
|
||||
}
|
||||
.map(|mode| Element::from(mode).map(Event::Message));
|
||||
.map(|setup| themer(theme(), Element::from(setup).map(Event::Message)));
|
||||
|
||||
let notification = self
|
||||
.show_notification
|
||||
|
|
@ -359,19 +362,19 @@ where
|
|||
})
|
||||
});
|
||||
|
||||
themer(
|
||||
theme,
|
||||
stack![view]
|
||||
.height(Fill)
|
||||
.push_maybe(setup.map(opaque))
|
||||
.push_maybe(notification.map(|notification| {
|
||||
themer(
|
||||
theme(),
|
||||
bottom_right(opaque(
|
||||
container(notification)
|
||||
.padding(10)
|
||||
.style(container::dark),
|
||||
))
|
||||
})),
|
||||
)),
|
||||
)
|
||||
}))
|
||||
.into()
|
||||
}
|
||||
|
||||
|
|
@ -394,7 +397,7 @@ where
|
|||
Subscription::batch([subscription, hotkeys, commands])
|
||||
}
|
||||
|
||||
pub fn theme(&self, program: &P, window: window::Id) -> P::Theme {
|
||||
pub fn theme(&self, program: &P, window: window::Id) -> Option<P::Theme> {
|
||||
program.theme(self.state(), window)
|
||||
}
|
||||
|
||||
|
|
@ -402,7 +405,7 @@ where
|
|||
program.style(self.state(), theme)
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self, program: &P, window: window::Id) -> f64 {
|
||||
pub fn scale_factor(&self, program: &P, window: window::Id) -> f32 {
|
||||
program.scale_factor(self.state(), window)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use iced::{Element, Fill, Point, Rectangle, Renderer, Subscription, Theme};
|
|||
pub fn main() -> iced::Result {
|
||||
iced::application(Arc::new, Arc::update, Arc::view)
|
||||
.subscription(Arc::subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.theme(Theme::Dark)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use iced::{Element, Theme};
|
|||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(Example::default, Example::update, Example::view)
|
||||
.theme(|_| Theme::CatppuccinMocha)
|
||||
.theme(Theme::CatppuccinMocha)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -128,26 +128,25 @@ impl Primitive {
|
|||
}
|
||||
|
||||
impl shader::Primitive for Primitive {
|
||||
fn prepare(
|
||||
type Renderer = Pipeline;
|
||||
|
||||
fn initialize(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
storage: &mut shader::Storage,
|
||||
) -> Pipeline {
|
||||
Pipeline::new(device, queue, format)
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&self,
|
||||
pipeline: &mut Pipeline,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
_bounds: &Rectangle,
|
||||
viewport: &Viewport,
|
||||
) {
|
||||
if !storage.has::<Pipeline>() {
|
||||
storage.store(Pipeline::new(
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
viewport.physical_size(),
|
||||
));
|
||||
}
|
||||
|
||||
let pipeline = storage.get_mut::<Pipeline>().unwrap();
|
||||
|
||||
// Upload data to GPU
|
||||
pipeline.update(
|
||||
device,
|
||||
|
|
@ -161,14 +160,11 @@ impl shader::Primitive for Primitive {
|
|||
|
||||
fn render(
|
||||
&self,
|
||||
pipeline: &Pipeline,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
storage: &shader::Storage,
|
||||
target: &wgpu::TextureView,
|
||||
clip_bounds: &Rectangle<u32>,
|
||||
) {
|
||||
// At this point our pipeline should always be initialized
|
||||
let pipeline = storage.get::<Pipeline>().unwrap();
|
||||
|
||||
// Render primitive
|
||||
pipeline.render(
|
||||
target,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ impl Pipeline {
|
|||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
target_size: Size<u32>,
|
||||
) -> Self {
|
||||
//vertices of one cube
|
||||
let vertices =
|
||||
|
|
@ -62,8 +61,8 @@ impl Pipeline {
|
|||
let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("cubes depth texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: target_size.width,
|
||||
height: target_size.height,
|
||||
width: 1,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
|
|
@ -297,7 +296,7 @@ impl Pipeline {
|
|||
uniforms,
|
||||
uniform_bind_group,
|
||||
vertices,
|
||||
depth_texture_size: target_size,
|
||||
depth_texture_size: Size::new(1, 1),
|
||||
depth_view,
|
||||
depth_pipeline,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -326,5 +326,8 @@ fn open_icon<'a, Message>() -> Element<'a, Message> {
|
|||
fn icon<'a, Message>(codepoint: char) -> Element<'a, Message> {
|
||||
const ICON_FONT: Font = Font::with_name("editor-icons");
|
||||
|
||||
text(codepoint).font(ICON_FONT).into()
|
||||
text(codepoint)
|
||||
.font(ICON_FONT)
|
||||
.shaping(text::Shaping::Basic)
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ impl Events {
|
|||
}
|
||||
Message::EventOccurred(event) => {
|
||||
if let Event::Window(window::Event::CloseRequested) = event {
|
||||
window::get_latest().and_then(window::close)
|
||||
window::latest().and_then(window::close)
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ impl Events {
|
|||
|
||||
Task::none()
|
||||
}
|
||||
Message::Exit => window::get_latest().and_then(window::close),
|
||||
Message::Exit => window::latest().and_then(window::close),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ enum Message {
|
|||
impl Exit {
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::Confirm => window::get_latest().and_then(window::close),
|
||||
Message::Confirm => window::latest().and_then(window::close),
|
||||
Message::Exit => {
|
||||
self.show_confirm = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use iced::{
|
|||
pub fn main() -> iced::Result {
|
||||
iced::application(Image::default, Image::update, Image::view)
|
||||
.subscription(Image::subscription)
|
||||
.theme(|_| Theme::TokyoNight)
|
||||
.theme(Theme::TokyoNight)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ pub fn main() -> iced::Result {
|
|||
|
||||
iced::application(GameOfLife::default, GameOfLife::update, GameOfLife::view)
|
||||
.subscription(GameOfLife::subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.theme(Theme::Dark)
|
||||
.centered()
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
let physical_size = window.inner_size();
|
||||
let viewport = Viewport::with_physical_size(
|
||||
Size::new(physical_size.width, physical_size.height),
|
||||
window.scale_factor(),
|
||||
window.scale_factor() as f32,
|
||||
);
|
||||
let clipboard = Clipboard::connect(window.clone());
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
|
||||
*viewport = Viewport::with_physical_size(
|
||||
Size::new(size.width, size.height),
|
||||
window.scale_factor(),
|
||||
window.scale_factor() as f32,
|
||||
);
|
||||
|
||||
surface.configure(
|
||||
|
|
@ -345,7 +345,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
// Map window event to iced event
|
||||
if let Some(event) = conversion::window_event(
|
||||
event,
|
||||
window.scale_factor(),
|
||||
window.scale_factor() as f32,
|
||||
*modifiers,
|
||||
) {
|
||||
events.push(event);
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ pub fn main() -> iced::Result {
|
|||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
struct Layout {
|
||||
example: Example,
|
||||
explain: bool,
|
||||
theme: Theme,
|
||||
theme: Option<Theme>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -51,7 +51,7 @@ impl Layout {
|
|||
self.explain = explain;
|
||||
}
|
||||
Message::ThemeSelected(theme) => {
|
||||
self.theme = theme;
|
||||
self.theme = Some(theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +74,8 @@ impl Layout {
|
|||
horizontal_space(),
|
||||
checkbox("Explain", self.explain)
|
||||
.on_toggle(Message::ExplainToggled),
|
||||
pick_list(Theme::ALL, Some(&self.theme), Message::ThemeSelected),
|
||||
pick_list(Theme::ALL, self.theme.as_ref(), Message::ThemeSelected)
|
||||
.placeholder("Theme"),
|
||||
]
|
||||
.spacing(20)
|
||||
.align_y(Center);
|
||||
|
|
@ -94,14 +95,14 @@ impl Layout {
|
|||
|
||||
let controls = row([
|
||||
(!self.example.is_first()).then_some(
|
||||
button(text("← Previous").shaping(text::Shaping::Advanced))
|
||||
button(text("← Previous"))
|
||||
.padding([5, 10])
|
||||
.on_press(Message::Previous)
|
||||
.into(),
|
||||
),
|
||||
Some(horizontal_space().into()),
|
||||
(!self.example.is_last()).then_some(
|
||||
button(text("Next →").shaping(text::Shaping::Advanced))
|
||||
button(text("Next →"))
|
||||
.padding([5, 10])
|
||||
.on_press(Message::Next)
|
||||
.into(),
|
||||
|
|
@ -116,7 +117,7 @@ impl Layout {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
fn theme(&self) -> Option<Theme> {
|
||||
self.theme.clone()
|
||||
}
|
||||
}
|
||||
|
|
@ -313,7 +314,7 @@ fn quotes<'a>() -> Element<'a, Message> {
|
|||
"This is another reply",
|
||||
),
|
||||
horizontal_rule(1),
|
||||
text("A separator ↑").shaping(text::Shaping::Advanced),
|
||||
text("A separator ↑"),
|
||||
]
|
||||
.width(Shrink)
|
||||
.spacing(10)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ struct Example {
|
|||
struct Window {
|
||||
title: String,
|
||||
scale_input: String,
|
||||
current_scale: f64,
|
||||
current_scale: f32,
|
||||
theme: Theme,
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ impl Example {
|
|||
return Task::none();
|
||||
};
|
||||
|
||||
window::get_position(*last_window)
|
||||
window::position(*last_window)
|
||||
.then(|last_position| {
|
||||
let position = last_position.map_or(
|
||||
window::Position::Default,
|
||||
|
|
@ -113,7 +113,7 @@ impl Example {
|
|||
Message::ScaleChanged(id, scale) => {
|
||||
if let Some(window) = self.windows.get_mut(&id) {
|
||||
window.current_scale = scale
|
||||
.parse::<f64>()
|
||||
.parse()
|
||||
.unwrap_or(window.current_scale)
|
||||
.clamp(0.5, 5.0);
|
||||
}
|
||||
|
|
@ -138,15 +138,11 @@ impl Example {
|
|||
}
|
||||
}
|
||||
|
||||
fn theme(&self, window: window::Id) -> Theme {
|
||||
if let Some(window) = self.windows.get(&window) {
|
||||
window.theme.clone()
|
||||
} else {
|
||||
Theme::default()
|
||||
}
|
||||
fn theme(&self, window: window::Id) -> Option<Theme> {
|
||||
Some(self.windows.get(&window)?.theme.clone())
|
||||
}
|
||||
|
||||
fn scale_factor(&self, window: window::Id) -> f64 {
|
||||
fn scale_factor(&self, window: window::Id) -> f32 {
|
||||
self.windows
|
||||
.get(&window)
|
||||
.map(|window| window.current_scale)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ struct QRGenerator {
|
|||
data: String,
|
||||
qr_code: Option<qr_code::Data>,
|
||||
total_size: Option<f32>,
|
||||
theme: Theme,
|
||||
theme: Option<Theme>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -58,7 +58,7 @@ impl QRGenerator {
|
|||
self.total_size = Some(total_size);
|
||||
}
|
||||
Message::ThemeChanged(theme) => {
|
||||
self.theme = theme;
|
||||
self.theme = Some(theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,7 +78,8 @@ impl QRGenerator {
|
|||
|
||||
let choose_theme = row![
|
||||
text("Theme:"),
|
||||
pick_list(Theme::ALL, Some(&self.theme), Message::ThemeChanged,)
|
||||
pick_list(Theme::ALL, self.theme.as_ref(), Message::ThemeChanged)
|
||||
.placeholder("Theme")
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(Center);
|
||||
|
|
@ -107,7 +108,7 @@ impl QRGenerator {
|
|||
center(content).padding(20).into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
fn theme(&self) -> Option<Theme> {
|
||||
self.theme.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ impl Example {
|
|||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::Screenshot => {
|
||||
return window::get_latest()
|
||||
return window::latest()
|
||||
.and_then(window::screenshot)
|
||||
.map(Message::Screenshotted);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0650eb2c27c21c5d48e1e00031a52d8471d8a3b4e827ad502c4628914f5c1c13
|
||||
129523830df064908cfa911214ba61dadc31d8975425ba3efaae69da9514a3d0
|
||||
|
|
@ -15,7 +15,7 @@ pub fn main() -> iced::Result {
|
|||
|
||||
#[derive(Default)]
|
||||
struct Styling {
|
||||
theme: Theme,
|
||||
theme: Option<Theme>,
|
||||
input_value: String,
|
||||
slider_value: f32,
|
||||
checkbox_value: bool,
|
||||
|
|
@ -32,13 +32,14 @@ enum Message {
|
|||
TogglerToggled(bool),
|
||||
PreviousTheme,
|
||||
NextTheme,
|
||||
ClearTheme,
|
||||
}
|
||||
|
||||
impl Styling {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::ThemeChanged(theme) => {
|
||||
self.theme = theme;
|
||||
self.theme = Some(theme);
|
||||
}
|
||||
Message::InputChanged(value) => self.input_value = value,
|
||||
Message::ButtonPressed => {}
|
||||
|
|
@ -46,21 +47,29 @@ impl Styling {
|
|||
Message::CheckboxToggled(value) => self.checkbox_value = value,
|
||||
Message::TogglerToggled(value) => self.toggler_value = value,
|
||||
Message::PreviousTheme | Message::NextTheme => {
|
||||
if let Some(current) = Theme::ALL
|
||||
.iter()
|
||||
.position(|candidate| &self.theme == candidate)
|
||||
{
|
||||
self.theme = if matches!(message, Message::NextTheme) {
|
||||
Theme::ALL[(current + 1) % Theme::ALL.len()].clone()
|
||||
} else if current == 0 {
|
||||
let current = Theme::ALL.iter().position(|candidate| {
|
||||
self.theme.as_ref() == Some(candidate)
|
||||
});
|
||||
|
||||
self.theme = Some(if matches!(message, Message::NextTheme) {
|
||||
Theme::ALL[current.map(|current| current + 1).unwrap_or(0)
|
||||
% Theme::ALL.len()]
|
||||
.clone()
|
||||
} else {
|
||||
let current = current.unwrap_or(0);
|
||||
|
||||
if current == 0 {
|
||||
Theme::ALL
|
||||
.last()
|
||||
.expect("Theme::ALL must not be empty")
|
||||
.clone()
|
||||
} else {
|
||||
Theme::ALL[current - 1].clone()
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
Message::ClearTheme => {
|
||||
self.theme = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,8 +77,9 @@ impl Styling {
|
|||
fn view(&self) -> Element<'_, Message> {
|
||||
let choose_theme = column![
|
||||
text("Theme:"),
|
||||
pick_list(Theme::ALL, Some(&self.theme), Message::ThemeChanged)
|
||||
.width(Fill),
|
||||
pick_list(Theme::ALL, self.theme.as_ref(), Message::ThemeChanged)
|
||||
.width(Fill)
|
||||
.placeholder("System"),
|
||||
]
|
||||
.spacing(10);
|
||||
|
||||
|
|
@ -186,11 +196,14 @@ impl Styling {
|
|||
keyboard::key::Named::ArrowDown
|
||||
| keyboard::key::Named::ArrowRight,
|
||||
) => Some(Message::NextTheme),
|
||||
keyboard::Key::Named(keyboard::key::Named::Space) => {
|
||||
Some(Message::ClearTheme)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
fn theme(&self) -> Option<Theme> {
|
||||
self.theme.clone()
|
||||
}
|
||||
}
|
||||
|
|
@ -210,9 +223,7 @@ mod tests {
|
|||
.cloned()
|
||||
.map(|theme| {
|
||||
let mut styling = Styling::default();
|
||||
styling.update(Message::ThemeChanged(theme));
|
||||
|
||||
let theme = styling.theme();
|
||||
styling.update(Message::ThemeChanged(theme.clone()));
|
||||
|
||||
let mut ui = simulator(styling.view());
|
||||
let snapshot = ui.snapshot(&theme)?;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced.workspace = true
|
||||
iced.features = ["system"]
|
||||
iced.features = ["sysinfo"]
|
||||
|
||||
bytesize = "1.1"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use iced::system;
|
||||
use iced::widget::{button, center, column, text};
|
||||
use iced::{Element, Task, system};
|
||||
use iced::{Element, Task};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(Example::new, Example::update, Example::view).run()
|
||||
|
|
@ -26,7 +27,7 @@ impl Example {
|
|||
fn new() -> (Self, Task<Message>) {
|
||||
(
|
||||
Self::Loading,
|
||||
system::fetch_information().map(Message::InformationReceived),
|
||||
system::information().map(Message::InformationReceived),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use iced::{Center, Element, Fill, Font, Right, Theme};
|
|||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(Table::new, Table::update, Table::view)
|
||||
.theme(|_| Theme::CatppuccinMocha)
|
||||
.theme(Theme::CatppuccinMocha)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
99f418007af163f172e163565f166da31015521e1bf7de95fa55cda2fb5a7db5
|
||||
0acb67235c6a11014a2d2b825e0a70069bca0c67bee0cdb38a0144fc72b25220
|
||||
|
|
@ -6,7 +6,7 @@ use iced::widget::{
|
|||
use iced::window;
|
||||
use iced::{
|
||||
Application, Center, Element, Fill, Font, Function, Preset, Program,
|
||||
Subscription, Task as Command,
|
||||
Subscription, Task as Command, Theme,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -156,7 +156,7 @@ impl Todos {
|
|||
operation::focus_next()
|
||||
}
|
||||
}
|
||||
Message::ToggleFullscreen(mode) => window::get_latest()
|
||||
Message::ToggleFullscreen(mode) => window::latest()
|
||||
.and_then(move |window| window::set_mode(window, mode)),
|
||||
Message::Loaded(_) => Command::none(),
|
||||
};
|
||||
|
|
@ -199,7 +199,7 @@ impl Todos {
|
|||
let title = text("todos")
|
||||
.width(Fill)
|
||||
.size(100)
|
||||
.color([0.5, 0.5, 0.5])
|
||||
.style(subtle)
|
||||
.align_x(Center);
|
||||
|
||||
let input = text_input("What needs to be done?", input_value)
|
||||
|
|
@ -452,7 +452,7 @@ fn empty_message(message: &str) -> Element<'_, Message> {
|
|||
.width(Fill)
|
||||
.size(25)
|
||||
.align_x(Center)
|
||||
.color([0.7, 0.7, 0.7]),
|
||||
.style(subtle),
|
||||
)
|
||||
.height(200)
|
||||
.into()
|
||||
|
|
@ -465,6 +465,7 @@ fn icon(unicode: char) -> Text<'static> {
|
|||
.font(Font::with_name("Iced-Todos-Icons"))
|
||||
.width(20)
|
||||
.align_x(Center)
|
||||
.shaping(text::Shaping::Basic)
|
||||
}
|
||||
|
||||
fn edit_icon() -> Text<'static> {
|
||||
|
|
@ -475,6 +476,12 @@ fn delete_icon() -> Text<'static> {
|
|||
icon('\u{F1F8}')
|
||||
}
|
||||
|
||||
fn subtle(theme: &Theme) -> text::Style {
|
||||
text::Style {
|
||||
color: Some(theme.extended_palette().background.strongest.color),
|
||||
}
|
||||
}
|
||||
|
||||
// Persistence
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct SavedState {
|
||||
|
|
@ -625,6 +632,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn it_creates_a_new_task() -> Result<(), Error> {
|
||||
let (mut todos, _command) = Todos::new();
|
||||
let _command = todos.update(Message::Loaded(Err(LoadError::File)));
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
use iced::border;
|
||||
use iced::widget::{Button, Column, Container, Slider};
|
||||
use iced::widget::{
|
||||
button, center_x, center_y, checkbox, column, horizontal_space, image,
|
||||
radio, rich_text, row, scrollable, slider, span, text, text_input, toggler,
|
||||
vertical_space,
|
||||
};
|
||||
use iced::{Center, Color, Element, Fill, Font, Pixels, Theme};
|
||||
use iced::{Center, Color, Element, Fill, Font, Pixels, color};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
|
@ -201,7 +200,7 @@ impl Tour {
|
|||
Self::container("Welcome!")
|
||||
.push(
|
||||
"This is a simple tour meant to showcase a bunch of \
|
||||
widgets that can be easily implemented on top of Iced.",
|
||||
widgets that come bundled in Iced.",
|
||||
)
|
||||
.push(
|
||||
"Iced is a cross-platform GUI library for Rust focused on \
|
||||
|
|
@ -216,28 +215,19 @@ impl Tour {
|
|||
built on top of wgpu, a graphics library supporting Vulkan, \
|
||||
Metal, DX11, and DX12.",
|
||||
)
|
||||
.push({
|
||||
let theme = Theme::default();
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
.push(
|
||||
rich_text![
|
||||
"Additionally, this tour can also run on WebAssembly ",
|
||||
"by leveraging ",
|
||||
span("trunk")
|
||||
.color(palette.primary.base.color)
|
||||
.background(palette.background.weakest.color)
|
||||
.border(
|
||||
border::rounded(2)
|
||||
.width(1)
|
||||
.color(palette.background.weak.color)
|
||||
)
|
||||
.padding([0, 2])
|
||||
.color(color!(0x7777FF))
|
||||
.underline(true)
|
||||
.font(Font::MONOSPACE)
|
||||
.link(Message::OpenTrunk),
|
||||
"."
|
||||
]
|
||||
.on_link_click(std::convert::identity)
|
||||
})
|
||||
.on_link_click(std::convert::identity),
|
||||
)
|
||||
.push(
|
||||
"You will need to interact with the UI in order to reach \
|
||||
the end!",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub fn main() -> iced::Result {
|
|||
VectorialText::update,
|
||||
VectorialText::view,
|
||||
)
|
||||
.theme(|_| Theme::Dark)
|
||||
.theme(Theme::Dark)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use iced::{
|
|||
pub fn main() -> iced::Result {
|
||||
iced::application(Example::default, Example::update, Example::view)
|
||||
.subscription(Example::subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.theme(Theme::Dark)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ where
|
|||
event: Event::Window(window::Event::RedrawRequested(_)),
|
||||
..
|
||||
}
|
||||
| subscription::Event::SystemThemeChanged(_)
|
||||
| subscription::Event::PlatformSpecific(_) => None,
|
||||
subscription::Event::Interaction {
|
||||
window,
|
||||
|
|
@ -66,7 +67,8 @@ where
|
|||
event,
|
||||
status,
|
||||
} => f(event, status, window),
|
||||
subscription::Event::PlatformSpecific(_) => None,
|
||||
subscription::Event::SystemThemeChanged(_)
|
||||
| subscription::Event::PlatformSpecific(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ mod tracker;
|
|||
pub use tracker::Tracker;
|
||||
|
||||
use crate::core::event;
|
||||
use crate::core::theme;
|
||||
use crate::core::window;
|
||||
use crate::futures::Stream;
|
||||
use crate::{BoxStream, MaybeSend};
|
||||
|
|
@ -27,6 +28,9 @@ pub enum Event {
|
|||
status: event::Status,
|
||||
},
|
||||
|
||||
/// The system theme has changed.
|
||||
SystemThemeChanged(theme::Mode),
|
||||
|
||||
/// A platform specific event.
|
||||
PlatformSpecific(PlatformSpecific),
|
||||
}
|
||||
|
|
@ -422,7 +426,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn filter_map<I, F, T>(id: I, f: F) -> Subscription<T>
|
||||
/// Creatges a [`Subscription`] from a hashable id and a filter function.
|
||||
pub fn filter_map<I, F, T>(id: I, f: F) -> Subscription<T>
|
||||
where
|
||||
I: Hash + 'static,
|
||||
F: Fn(Event) -> Option<T> + MaybeSend + 'static,
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ pub trait Compositor: Sized {
|
|||
);
|
||||
|
||||
/// Returns [`Information`] used by this [`Compositor`].
|
||||
fn fetch_information(&self) -> Information;
|
||||
fn information(&self) -> Information;
|
||||
|
||||
/// Loads a font from its bytes.
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
||||
|
|
@ -178,7 +178,7 @@ impl Compositor for () {
|
|||
|
||||
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
||||
|
||||
fn fetch_information(&self) -> Information {
|
||||
fn information(&self) -> Information {
|
||||
Information {
|
||||
adapter: String::from("Null Renderer"),
|
||||
backend: String::from("Null"),
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ impl Default for Text {
|
|||
font: Font::default(),
|
||||
align_x: Alignment::Default,
|
||||
align_y: alignment::Vertical::Top,
|
||||
shaping: Shaping::Basic,
|
||||
shaping: Shaping::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -323,8 +323,15 @@ fn to_align(alignment: Alignment) -> Option<cosmic_text::Align> {
|
|||
}
|
||||
|
||||
/// Converts some [`Shaping`] strategy to a [`cosmic_text::Shaping`] strategy.
|
||||
pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping {
|
||||
pub fn to_shaping(shaping: Shaping, text: &str) -> cosmic_text::Shaping {
|
||||
match shaping {
|
||||
Shaping::Auto => {
|
||||
if text.is_ascii() {
|
||||
cosmic_text::Shaping::Basic
|
||||
} else {
|
||||
cosmic_text::Shaping::Advanced
|
||||
}
|
||||
}
|
||||
Shaping::Basic => cosmic_text::Shaping::Basic,
|
||||
Shaping::Advanced => cosmic_text::Shaping::Advanced,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ impl Cache {
|
|||
font_system,
|
||||
key.content,
|
||||
&text::to_attributes(key.font),
|
||||
text::to_shaping(key.shaping),
|
||||
text::to_shaping(key.shaping, key.content),
|
||||
);
|
||||
|
||||
let bounds = text::align(&mut buffer, font_system, key.align_x);
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ impl core::text::Paragraph for Paragraph {
|
|||
font_system.raw(),
|
||||
text.content,
|
||||
&text::to_attributes(text.font),
|
||||
text::to_shaping(text.shaping),
|
||||
text::to_shaping(text.shaping, text.content),
|
||||
);
|
||||
|
||||
let min_bounds =
|
||||
|
|
@ -158,7 +158,7 @@ impl core::text::Paragraph for Paragraph {
|
|||
(span.text.as_ref(), attrs.metadata(i))
|
||||
}),
|
||||
&text::to_attributes(text.font),
|
||||
text::to_shaping(text.shaping),
|
||||
cosmic_text::Shaping::Advanced,
|
||||
None,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,19 +5,19 @@ use crate::core::{Size, Transformation};
|
|||
pub struct Viewport {
|
||||
physical_size: Size<u32>,
|
||||
logical_size: Size<f32>,
|
||||
scale_factor: f64,
|
||||
scale_factor: f32,
|
||||
projection: Transformation,
|
||||
}
|
||||
|
||||
impl Viewport {
|
||||
/// Creates a new [`Viewport`] with the given physical dimensions and scale
|
||||
/// factor.
|
||||
pub fn with_physical_size(size: Size<u32>, scale_factor: f64) -> Viewport {
|
||||
pub fn with_physical_size(size: Size<u32>, scale_factor: f32) -> Viewport {
|
||||
Viewport {
|
||||
physical_size: size,
|
||||
logical_size: Size::new(
|
||||
(size.width as f64 / scale_factor) as f32,
|
||||
(size.height as f64 / scale_factor) as f32,
|
||||
size.width as f32 / scale_factor,
|
||||
size.height as f32 / scale_factor,
|
||||
),
|
||||
scale_factor,
|
||||
projection: Transformation::orthographic(size.width, size.height),
|
||||
|
|
@ -45,7 +45,7 @@ impl Viewport {
|
|||
}
|
||||
|
||||
/// Returns the scale factor of the [`Viewport`].
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
pub fn scale_factor(&self) -> f32 {
|
||||
self.scale_factor
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ pub trait Program: Sized {
|
|||
type Message: Send + 'static;
|
||||
|
||||
/// The theme of the program.
|
||||
type Theme: Default + theme::Base;
|
||||
type Theme: theme::Base;
|
||||
|
||||
/// The renderer of the program.
|
||||
type Renderer: Renderer;
|
||||
|
|
@ -97,15 +97,19 @@ pub trait Program: Sized {
|
|||
Subscription::none()
|
||||
}
|
||||
|
||||
fn theme(&self, _state: &Self::State, _window: window::Id) -> Self::Theme {
|
||||
<Self::Theme as Default>::default()
|
||||
fn theme(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
_window: window::Id,
|
||||
) -> Option<Self::Theme> {
|
||||
None
|
||||
}
|
||||
|
||||
fn style(&self, _state: &Self::State, theme: &Self::Theme) -> theme::Style {
|
||||
theme::Base::base(theme)
|
||||
}
|
||||
|
||||
fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f64 {
|
||||
fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +179,7 @@ pub fn with_title<P: Program>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
self.program.theme(state, window)
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +198,7 @@ pub fn with_title<P: Program>(
|
|||
self.program.style(state, theme)
|
||||
}
|
||||
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
|
||||
self.program.scale_factor(state, window)
|
||||
}
|
||||
}
|
||||
|
|
@ -269,7 +273,7 @@ pub fn with_subscription<P: Program>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
self.program.theme(state, window)
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +285,7 @@ pub fn with_subscription<P: Program>(
|
|||
self.program.style(state, theme)
|
||||
}
|
||||
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
|
||||
self.program.scale_factor(state, window)
|
||||
}
|
||||
}
|
||||
|
|
@ -295,7 +299,7 @@ pub fn with_subscription<P: Program>(
|
|||
/// Decorates a [`Program`] with the given theme function.
|
||||
pub fn with_theme<P: Program>(
|
||||
program: P,
|
||||
f: impl Fn(&P::State, window::Id) -> P::Theme,
|
||||
f: impl Fn(&P::State, window::Id) -> Option<P::Theme>,
|
||||
) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> {
|
||||
struct WithTheme<P, F> {
|
||||
program: P,
|
||||
|
|
@ -304,7 +308,7 @@ pub fn with_theme<P: Program>(
|
|||
|
||||
impl<P: Program, F> Program for WithTheme<P, F>
|
||||
where
|
||||
F: Fn(&P::State, window::Id) -> P::Theme,
|
||||
F: Fn(&P::State, window::Id) -> Option<P::Theme>,
|
||||
{
|
||||
type State = P::State;
|
||||
type Message = P::Message;
|
||||
|
|
@ -316,7 +320,7 @@ pub fn with_theme<P: Program>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
(self.theme)(state, window)
|
||||
}
|
||||
|
||||
|
|
@ -371,7 +375,7 @@ pub fn with_theme<P: Program>(
|
|||
self.program.style(state, theme)
|
||||
}
|
||||
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
|
||||
self.program.scale_factor(state, window)
|
||||
}
|
||||
}
|
||||
|
|
@ -454,11 +458,11 @@ pub fn with_style<P: Program>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
self.program.theme(state, window)
|
||||
}
|
||||
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
|
||||
self.program.scale_factor(state, window)
|
||||
}
|
||||
}
|
||||
|
|
@ -469,7 +473,7 @@ pub fn with_style<P: Program>(
|
|||
/// Decorates a [`Program`] with the given scale factor function.
|
||||
pub fn with_scale_factor<P: Program>(
|
||||
program: P,
|
||||
f: impl Fn(&P::State, window::Id) -> f64,
|
||||
f: impl Fn(&P::State, window::Id) -> f32,
|
||||
) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> {
|
||||
struct WithScaleFactor<P, F> {
|
||||
program: P,
|
||||
|
|
@ -478,7 +482,7 @@ pub fn with_scale_factor<P: Program>(
|
|||
|
||||
impl<P: Program, F> Program for WithScaleFactor<P, F>
|
||||
where
|
||||
F: Fn(&P::State, window::Id) -> f64,
|
||||
F: Fn(&P::State, window::Id) -> f32,
|
||||
{
|
||||
type State = P::State;
|
||||
type Message = P::Message;
|
||||
|
|
@ -533,7 +537,7 @@ pub fn with_scale_factor<P: Program>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
self.program.theme(state, window)
|
||||
}
|
||||
|
||||
|
|
@ -545,7 +549,7 @@ pub fn with_scale_factor<P: Program>(
|
|||
self.program.style(state, theme)
|
||||
}
|
||||
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
|
||||
(self.scale_factor)(state, window)
|
||||
}
|
||||
}
|
||||
|
|
@ -624,7 +628,7 @@ pub fn with_executor<P: Program, E: Executor>(
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
self.program.theme(state, window)
|
||||
}
|
||||
|
||||
|
|
@ -636,7 +640,7 @@ pub fn with_executor<P: Program, E: Executor>(
|
|||
self.program.style(state, theme)
|
||||
}
|
||||
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
|
||||
self.program.scale_factor(state, window)
|
||||
}
|
||||
}
|
||||
|
|
@ -697,7 +701,7 @@ impl<P: Program> Instance<P> {
|
|||
}
|
||||
|
||||
/// Returns the current theme of the [`Instance`].
|
||||
pub fn theme(&self, window: window::Id) -> P::Theme {
|
||||
pub fn theme(&self, window: window::Id) -> Option<P::Theme> {
|
||||
self.program.theme(&self.state, window)
|
||||
}
|
||||
|
||||
|
|
@ -707,7 +711,7 @@ impl<P: Program> Instance<P> {
|
|||
}
|
||||
|
||||
/// Returns the current scale factor of the [`Instance`].
|
||||
pub fn scale_factor(&self, window: window::Id) -> f64 {
|
||||
pub fn scale_factor(&self, window: window::Id) -> f32 {
|
||||
self.program.scale_factor(&self.state, window)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -312,8 +312,8 @@ where
|
|||
delegate!(self, compositor, compositor.load_font(font));
|
||||
}
|
||||
|
||||
fn fetch_information(&self) -> compositor::Information {
|
||||
delegate!(self, compositor, compositor.fetch_information())
|
||||
fn information(&self) -> compositor::Information {
|
||||
delegate!(self, compositor, compositor.information())
|
||||
}
|
||||
|
||||
fn present(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
//! Access the native system.
|
||||
use crate::core::theme;
|
||||
use crate::futures::futures::channel::oneshot;
|
||||
use crate::futures::subscription::{self, Subscription};
|
||||
use crate::task::{self, Task};
|
||||
|
||||
/// An operation to be performed on the system.
|
||||
#[derive(Debug)]
|
||||
pub enum Action {
|
||||
/// Query system information and produce `T` with the result.
|
||||
QueryInformation(oneshot::Sender<Information>),
|
||||
/// Send available system information.
|
||||
GetInformation(oneshot::Sender<Information>),
|
||||
|
||||
/// Send the current system theme mode.
|
||||
GetTheme(oneshot::Sender<theme::Mode>),
|
||||
|
||||
/// Notify to the runtime that the system theme has changed.
|
||||
NotifyTheme(theme::Mode),
|
||||
}
|
||||
|
||||
/// Contains information about the system (e.g. system name, processor, memory, graphics adapter).
|
||||
|
|
@ -37,3 +46,29 @@ pub struct Information {
|
|||
/// Model information for the active graphics adapter
|
||||
pub graphics_adapter: String,
|
||||
}
|
||||
|
||||
/// Returns available system information.
|
||||
pub fn information() -> Task<Information> {
|
||||
task::oneshot(|channel| {
|
||||
crate::Action::System(Action::GetInformation(channel))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the current system theme.
|
||||
pub fn theme() -> Task<theme::Mode> {
|
||||
task::oneshot(|sender| crate::Action::System(Action::GetTheme(sender)))
|
||||
}
|
||||
|
||||
/// Subscribes to system theme changes.
|
||||
pub fn theme_changes() -> Subscription<theme::Mode> {
|
||||
#[derive(Hash)]
|
||||
struct ThemeChanges;
|
||||
|
||||
subscription::filter_map(ThemeChanges, |event| {
|
||||
let subscription::Event::SystemThemeChanged(mode) = event else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(mode)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,12 +266,12 @@ pub fn close<T>(id: Id) -> Task<T> {
|
|||
}
|
||||
|
||||
/// Gets the window [`Id`] of the oldest window.
|
||||
pub fn get_oldest() -> Task<Option<Id>> {
|
||||
pub fn oldest() -> Task<Option<Id>> {
|
||||
task::oneshot(|channel| crate::Action::Window(Action::GetOldest(channel)))
|
||||
}
|
||||
|
||||
/// Gets the window [`Id`] of the latest window.
|
||||
pub fn get_latest() -> Task<Option<Id>> {
|
||||
pub fn latest() -> Task<Option<Id>> {
|
||||
task::oneshot(|channel| crate::Action::Window(Action::GetLatest(channel)))
|
||||
}
|
||||
|
||||
|
|
@ -315,14 +315,14 @@ pub fn set_resize_increments<T>(id: Id, increments: Option<Size>) -> Task<T> {
|
|||
}
|
||||
|
||||
/// Get the window's size in logical dimensions.
|
||||
pub fn get_size(id: Id) -> Task<Size> {
|
||||
pub fn size(id: Id) -> Task<Size> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetSize(id, channel))
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the maximized state of the window with the given [`Id`].
|
||||
pub fn get_maximized(id: Id) -> Task<bool> {
|
||||
pub fn is_maximized(id: Id) -> Task<bool> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetMaximized(id, channel))
|
||||
})
|
||||
|
|
@ -334,7 +334,7 @@ pub fn maximize<T>(id: Id, maximized: bool) -> Task<T> {
|
|||
}
|
||||
|
||||
/// Gets the minimized state of the window with the given [`Id`].
|
||||
pub fn get_minimized(id: Id) -> Task<Option<bool>> {
|
||||
pub fn is_minimized(id: Id) -> Task<Option<bool>> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetMinimized(id, channel))
|
||||
})
|
||||
|
|
@ -346,14 +346,14 @@ pub fn minimize<T>(id: Id, minimized: bool) -> Task<T> {
|
|||
}
|
||||
|
||||
/// Gets the position in logical coordinates of the window with the given [`Id`].
|
||||
pub fn get_position(id: Id) -> Task<Option<Point>> {
|
||||
pub fn position(id: Id) -> Task<Option<Point>> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetPosition(id, channel))
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the scale factor of the window with the given [`Id`].
|
||||
pub fn get_scale_factor(id: Id) -> Task<f32> {
|
||||
pub fn scale_factor(id: Id) -> Task<f32> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetScaleFactor(id, channel))
|
||||
})
|
||||
|
|
@ -365,7 +365,7 @@ pub fn move_to<T>(id: Id, position: Point) -> Task<T> {
|
|||
}
|
||||
|
||||
/// Gets the current [`Mode`] of the window.
|
||||
pub fn get_mode(id: Id) -> Task<Mode> {
|
||||
pub fn mode(id: Id) -> Task<Mode> {
|
||||
task::oneshot(move |channel| {
|
||||
crate::Action::Window(Action::GetMode(id, channel))
|
||||
})
|
||||
|
|
@ -426,7 +426,7 @@ pub fn show_system_menu<T>(id: Id) -> Task<T> {
|
|||
|
||||
/// Gets an identifier unique to the window, provided by the underlying windowing system. This is
|
||||
/// not to be confused with [`Id`].
|
||||
pub fn get_raw_id<Message>(id: Id) -> Task<u64> {
|
||||
pub fn raw_id<Message>(id: Id) -> Task<u64> {
|
||||
task::oneshot(|channel| {
|
||||
crate::Action::Window(Action::GetRawId(id, channel))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//!
|
||||
//! pub fn main() -> iced::Result {
|
||||
//! iced::application(u64::default, update, view)
|
||||
//! .theme(|_| Theme::Dark)
|
||||
//! .theme(Theme::Dark)
|
||||
//! .centered()
|
||||
//! .run()
|
||||
//! }
|
||||
|
|
@ -36,7 +36,8 @@ use crate::shell;
|
|||
use crate::theme;
|
||||
use crate::window;
|
||||
use crate::{
|
||||
Element, Executor, Font, Preset, Result, Settings, Size, Subscription, Task,
|
||||
Element, Executor, Font, Preset, Result, Settings, Size, Subscription,
|
||||
Task, Theme,
|
||||
};
|
||||
|
||||
use iced_debug as debug;
|
||||
|
|
@ -76,14 +77,14 @@ pub use timed::timed;
|
|||
/// }
|
||||
/// ```
|
||||
pub fn application<State, Message, Theme, Renderer>(
|
||||
boot: impl Boot<State, Message>,
|
||||
update: impl Update<State, Message>,
|
||||
view: impl for<'a> View<'a, State, Message, Theme, Renderer>,
|
||||
boot: impl BootFn<State, Message>,
|
||||
update: impl UpdateFn<State, Message>,
|
||||
view: impl for<'a> ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
|
||||
where
|
||||
State: 'static,
|
||||
Message: Send + 'static,
|
||||
Theme: Default + theme::Base,
|
||||
Theme: theme::Base,
|
||||
Renderer: program::Renderer,
|
||||
{
|
||||
use std::marker::PhantomData;
|
||||
|
|
@ -102,11 +103,11 @@ where
|
|||
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||
where
|
||||
Message: Send + 'static,
|
||||
Theme: Default + theme::Base,
|
||||
Theme: theme::Base,
|
||||
Renderer: program::Renderer,
|
||||
Boot: self::Boot<State, Message>,
|
||||
Update: self::Update<State, Message>,
|
||||
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||
Boot: self::BootFn<State, Message>,
|
||||
Update: self::UpdateFn<State, Message>,
|
||||
View: for<'a> self::ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
{
|
||||
type State = State;
|
||||
type Message = Message;
|
||||
|
|
@ -334,10 +335,10 @@ impl<P: Program> Application<P> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets the [`Title`] of the [`Application`].
|
||||
/// Sets the title of the [`Application`].
|
||||
pub fn title(
|
||||
self,
|
||||
title: impl Title<P::State>,
|
||||
title: impl TitleFn<P::State>,
|
||||
) -> Application<
|
||||
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
|
||||
> {
|
||||
|
|
@ -369,12 +370,14 @@ impl<P: Program> Application<P> {
|
|||
/// Sets the theme logic of the [`Application`].
|
||||
pub fn theme(
|
||||
self,
|
||||
f: impl Fn(&P::State) -> P::Theme,
|
||||
f: impl ThemeFn<P::State, P::Theme>,
|
||||
) -> Application<
|
||||
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
|
||||
> {
|
||||
Application {
|
||||
raw: program::with_theme(self.raw, move |state, _window| f(state)),
|
||||
raw: program::with_theme(self.raw, move |state, _window| {
|
||||
f.theme(state)
|
||||
}),
|
||||
settings: self.settings,
|
||||
window: self.window,
|
||||
presets: self.presets,
|
||||
|
|
@ -399,7 +402,7 @@ impl<P: Program> Application<P> {
|
|||
/// Sets the scale factor of the [`Application`].
|
||||
pub fn scale_factor(
|
||||
self,
|
||||
f: impl Fn(&P::State) -> f64,
|
||||
f: impl Fn(&P::State) -> f32,
|
||||
) -> Application<
|
||||
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
|
||||
> {
|
||||
|
|
@ -497,7 +500,7 @@ impl<P: Program> Program for Application<P> {
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: iced_core::window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
debug::hot(|| self.raw.theme(state, window))
|
||||
}
|
||||
|
||||
|
|
@ -505,7 +508,7 @@ impl<P: Program> Program for Application<P> {
|
|||
debug::hot(|| self.raw.style(state, theme))
|
||||
}
|
||||
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
|
||||
debug::hot(|| self.raw.scale_factor(state, window))
|
||||
}
|
||||
|
||||
|
|
@ -522,12 +525,12 @@ impl<P: Program> Program for Application<P> {
|
|||
/// In practice, this means that [`application`] can both take
|
||||
/// simple functions like `State::default` and more advanced ones
|
||||
/// that return a [`Task`].
|
||||
pub trait Boot<State, Message> {
|
||||
pub trait BootFn<State, Message> {
|
||||
/// Initializes the [`Application`] state.
|
||||
fn boot(&self) -> (State, Task<Message>);
|
||||
}
|
||||
|
||||
impl<T, C, State, Message> Boot<State, Message> for T
|
||||
impl<T, C, State, Message> BootFn<State, Message> for T
|
||||
where
|
||||
T: Fn() -> C,
|
||||
C: IntoBoot<State, Message>,
|
||||
|
|
@ -561,18 +564,18 @@ impl<State, Message> IntoBoot<State, Message> for (State, Task<Message>) {
|
|||
/// any closure `Fn(&State) -> String`.
|
||||
///
|
||||
/// This trait allows the [`application`] builder to take any of them.
|
||||
pub trait Title<State> {
|
||||
pub trait TitleFn<State> {
|
||||
/// Produces the title of the [`Application`].
|
||||
fn title(&self, state: &State) -> String;
|
||||
}
|
||||
|
||||
impl<State> Title<State> for &'static str {
|
||||
impl<State> TitleFn<State> for &'static str {
|
||||
fn title(&self, _state: &State) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, State> Title<State> for T
|
||||
impl<T, State> TitleFn<State> for T
|
||||
where
|
||||
T: Fn(&State) -> String,
|
||||
{
|
||||
|
|
@ -585,18 +588,18 @@ where
|
|||
///
|
||||
/// This trait allows the [`application`] builder to take any closure that
|
||||
/// returns any `Into<Task<Message>>`.
|
||||
pub trait Update<State, Message> {
|
||||
pub trait UpdateFn<State, Message> {
|
||||
/// Processes the message and updates the state of the [`Application`].
|
||||
fn update(&self, state: &mut State, message: Message) -> Task<Message>;
|
||||
}
|
||||
|
||||
impl<State, Message> Update<State, Message> for () {
|
||||
impl<State, Message> UpdateFn<State, Message> for () {
|
||||
fn update(&self, _state: &mut State, _message: Message) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, State, Message, C> Update<State, Message> for T
|
||||
impl<T, State, Message, C> UpdateFn<State, Message> for T
|
||||
where
|
||||
T: Fn(&mut State, Message) -> C,
|
||||
C: Into<Task<Message>>,
|
||||
|
|
@ -610,13 +613,13 @@ where
|
|||
///
|
||||
/// This trait allows the [`application`] builder to take any closure that
|
||||
/// returns any `Into<Element<'_, Message>>`.
|
||||
pub trait View<'a, State, Message, Theme, Renderer> {
|
||||
pub trait ViewFn<'a, State, Message, Theme, Renderer> {
|
||||
/// Produces the widget of the [`Application`].
|
||||
fn view(&self, state: &'a State) -> Element<'a, Message, Theme, Renderer>;
|
||||
}
|
||||
|
||||
impl<'a, T, State, Message, Theme, Renderer, Widget>
|
||||
View<'a, State, Message, Theme, Renderer> for T
|
||||
ViewFn<'a, State, Message, Theme, Renderer> for T
|
||||
where
|
||||
T: Fn(&'a State) -> Widget,
|
||||
State: 'static,
|
||||
|
|
@ -626,3 +629,35 @@ where
|
|||
self(state).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The theme logic of some [`Application`].
|
||||
///
|
||||
/// Any implementors of this trait can be provided as an argument to
|
||||
/// [`Application::theme`].
|
||||
///
|
||||
/// `iced` provides two implementors:
|
||||
/// - the built-in [`Theme`] itself
|
||||
/// - and any `Fn(&State) -> impl Into<Option<Theme>>`.
|
||||
pub trait ThemeFn<State, Theme> {
|
||||
/// Returns the theme of the [`Application`] for the current state.
|
||||
///
|
||||
/// If `None` is returned, `iced` will try to use a theme that
|
||||
/// matches the system color scheme.
|
||||
fn theme(&self, state: &State) -> Option<Theme>;
|
||||
}
|
||||
|
||||
impl<State> ThemeFn<State, Theme> for Theme {
|
||||
fn theme(&self, _state: &State) -> Option<Theme> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, State, Theme> ThemeFn<State, Theme> for F
|
||||
where
|
||||
F: Fn(&State) -> T,
|
||||
T: Into<Option<Theme>>,
|
||||
{
|
||||
fn theme(&self, state: &State) -> Option<Theme> {
|
||||
(self)(state).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//! An [`Application`] that receives an [`Instant`] in update logic.
|
||||
use crate::application::{Application, Boot, View};
|
||||
use crate::application::{Application, BootFn, ViewFn};
|
||||
use crate::program;
|
||||
use crate::theme;
|
||||
use crate::time::Instant;
|
||||
|
|
@ -20,17 +20,17 @@ use iced_debug as debug;
|
|||
///
|
||||
/// [`comet`]: https://github.com/iced-rs/comet
|
||||
pub fn timed<State, Message, Theme, Renderer>(
|
||||
boot: impl Boot<State, Message>,
|
||||
update: impl Update<State, Message>,
|
||||
boot: impl BootFn<State, Message>,
|
||||
update: impl UpdateFn<State, Message>,
|
||||
subscription: impl Fn(&State) -> Subscription<Message>,
|
||||
view: impl for<'a> View<'a, State, Message, Theme, Renderer>,
|
||||
view: impl for<'a> ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
) -> Application<
|
||||
impl Program<State = State, Message = (Message, Instant), Theme = Theme>,
|
||||
>
|
||||
where
|
||||
State: 'static,
|
||||
Message: Send + 'static,
|
||||
Theme: Default + theme::Base + 'static,
|
||||
Theme: theme::Base + 'static,
|
||||
Renderer: program::Renderer + 'static,
|
||||
{
|
||||
use std::marker::PhantomData;
|
||||
|
|
@ -69,12 +69,12 @@ where
|
|||
>
|
||||
where
|
||||
Message: Send + 'static,
|
||||
Theme: Default + theme::Base + 'static,
|
||||
Theme: theme::Base + 'static,
|
||||
Renderer: program::Renderer + 'static,
|
||||
Boot: self::Boot<State, Message>,
|
||||
Update: self::Update<State, Message>,
|
||||
Boot: self::BootFn<State, Message>,
|
||||
Update: self::UpdateFn<State, Message>,
|
||||
Subscription: Fn(&State) -> self::Subscription<Message>,
|
||||
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||
View: for<'a> self::ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
{
|
||||
type State = State;
|
||||
type Message = (Message, Instant);
|
||||
|
|
@ -157,9 +157,9 @@ where
|
|||
|
||||
/// The update logic of some timed [`Application`].
|
||||
///
|
||||
/// This is like [`application::Update`](super::Update),
|
||||
/// This is like [`application::UpdateFn`](super::UpdateFn),
|
||||
/// but it also takes an [`Instant`].
|
||||
pub trait Update<State, Message> {
|
||||
pub trait UpdateFn<State, Message> {
|
||||
/// Processes the message and updates the state of the [`Application`].
|
||||
fn update(
|
||||
&self,
|
||||
|
|
@ -169,7 +169,7 @@ pub trait Update<State, Message> {
|
|||
) -> impl Into<Task<Message>>;
|
||||
}
|
||||
|
||||
impl<State, Message> Update<State, Message> for () {
|
||||
impl<State, Message> UpdateFn<State, Message> for () {
|
||||
fn update(
|
||||
&self,
|
||||
_state: &mut State,
|
||||
|
|
@ -179,7 +179,7 @@ impl<State, Message> Update<State, Message> for () {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, State, Message, C> Update<State, Message> for T
|
||||
impl<T, State, Message, C> UpdateFn<State, Message> for T
|
||||
where
|
||||
T: Fn(&mut State, Message, Instant) -> C,
|
||||
C: Into<Task<Message>>,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use crate::theme;
|
|||
use crate::window;
|
||||
use crate::{
|
||||
Element, Executor, Font, Preset, Result, Settings, Subscription, Task,
|
||||
Theme,
|
||||
};
|
||||
|
||||
use iced_debug as debug;
|
||||
|
|
@ -24,14 +25,14 @@ use std::borrow::Cow;
|
|||
///
|
||||
/// [`exit`]: crate::exit
|
||||
pub fn daemon<State, Message, Theme, Renderer>(
|
||||
boot: impl application::Boot<State, Message>,
|
||||
update: impl application::Update<State, Message>,
|
||||
view: impl for<'a> View<'a, State, Message, Theme, Renderer>,
|
||||
boot: impl application::BootFn<State, Message>,
|
||||
update: impl application::UpdateFn<State, Message>,
|
||||
view: impl for<'a> ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>>
|
||||
where
|
||||
State: 'static,
|
||||
Message: Send + 'static,
|
||||
Theme: Default + theme::Base,
|
||||
Theme: theme::Base,
|
||||
Renderer: program::Renderer,
|
||||
{
|
||||
use std::marker::PhantomData;
|
||||
|
|
@ -50,11 +51,11 @@ where
|
|||
for Instance<State, Message, Theme, Renderer, Boot, Update, View>
|
||||
where
|
||||
Message: Send + 'static,
|
||||
Theme: Default + theme::Base,
|
||||
Theme: theme::Base,
|
||||
Renderer: program::Renderer,
|
||||
Boot: application::Boot<State, Message>,
|
||||
Update: application::Update<State, Message>,
|
||||
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
|
||||
Boot: application::BootFn<State, Message>,
|
||||
Update: application::UpdateFn<State, Message>,
|
||||
View: for<'a> self::ViewFn<'a, State, Message, Theme, Renderer>,
|
||||
{
|
||||
type State = State;
|
||||
type Message = Message;
|
||||
|
|
@ -183,10 +184,10 @@ impl<P: Program> Daemon<P> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Title`] of the [`Daemon`].
|
||||
/// Sets the title of the [`Daemon`].
|
||||
pub fn title(
|
||||
self,
|
||||
title: impl Title<P::State>,
|
||||
title: impl TitleFn<P::State>,
|
||||
) -> Daemon<
|
||||
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
|
||||
> {
|
||||
|
|
@ -216,12 +217,14 @@ impl<P: Program> Daemon<P> {
|
|||
/// Sets the theme logic of the [`Daemon`].
|
||||
pub fn theme(
|
||||
self,
|
||||
f: impl Fn(&P::State, window::Id) -> P::Theme,
|
||||
f: impl ThemeFn<P::State, P::Theme>,
|
||||
) -> Daemon<
|
||||
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
|
||||
> {
|
||||
Daemon {
|
||||
raw: program::with_theme(self.raw, f),
|
||||
raw: program::with_theme(self.raw, move |state, window| {
|
||||
f.theme(state, window)
|
||||
}),
|
||||
settings: self.settings,
|
||||
presets: self.presets,
|
||||
}
|
||||
|
|
@ -244,7 +247,7 @@ impl<P: Program> Daemon<P> {
|
|||
/// Sets the scale factor of the [`Daemon`].
|
||||
pub fn scale_factor(
|
||||
self,
|
||||
f: impl Fn(&P::State, window::Id) -> f64,
|
||||
f: impl Fn(&P::State, window::Id) -> f32,
|
||||
) -> Daemon<
|
||||
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
|
||||
> {
|
||||
|
|
@ -338,7 +341,7 @@ impl<P: Program> Program for Daemon<P> {
|
|||
&self,
|
||||
state: &Self::State,
|
||||
window: iced_core::window::Id,
|
||||
) -> Self::Theme {
|
||||
) -> Option<Self::Theme> {
|
||||
debug::hot(|| self.raw.theme(state, window))
|
||||
}
|
||||
|
||||
|
|
@ -346,7 +349,7 @@ impl<P: Program> Program for Daemon<P> {
|
|||
debug::hot(|| self.raw.style(state, theme))
|
||||
}
|
||||
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
|
||||
fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
|
||||
debug::hot(|| self.raw.scale_factor(state, window))
|
||||
}
|
||||
|
||||
|
|
@ -361,18 +364,18 @@ impl<P: Program> Program for Daemon<P> {
|
|||
/// any closure `Fn(&State, window::Id) -> String`.
|
||||
///
|
||||
/// This trait allows the [`daemon`] builder to take any of them.
|
||||
pub trait Title<State> {
|
||||
pub trait TitleFn<State> {
|
||||
/// Produces the title of the [`Daemon`].
|
||||
fn title(&self, state: &State, window: window::Id) -> String;
|
||||
}
|
||||
|
||||
impl<State> Title<State> for &'static str {
|
||||
impl<State> TitleFn<State> for &'static str {
|
||||
fn title(&self, _state: &State, _window: window::Id) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, State> Title<State> for T
|
||||
impl<T, State> TitleFn<State> for T
|
||||
where
|
||||
T: Fn(&State, window::Id) -> String,
|
||||
{
|
||||
|
|
@ -385,7 +388,7 @@ where
|
|||
///
|
||||
/// This trait allows the [`daemon`] builder to take any closure that
|
||||
/// returns any `Into<Element<'_, Message>>`.
|
||||
pub trait View<'a, State, Message, Theme, Renderer> {
|
||||
pub trait ViewFn<'a, State, Message, Theme, Renderer> {
|
||||
/// Produces the widget of the [`Daemon`].
|
||||
fn view(
|
||||
&self,
|
||||
|
|
@ -395,7 +398,7 @@ pub trait View<'a, State, Message, Theme, Renderer> {
|
|||
}
|
||||
|
||||
impl<'a, T, State, Message, Theme, Renderer, Widget>
|
||||
View<'a, State, Message, Theme, Renderer> for T
|
||||
ViewFn<'a, State, Message, Theme, Renderer> for T
|
||||
where
|
||||
T: Fn(&'a State, window::Id) -> Widget,
|
||||
State: 'static,
|
||||
|
|
@ -409,3 +412,35 @@ where
|
|||
self(state, window).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The theme logic of some [`Daemon`].
|
||||
///
|
||||
/// Any implementors of this trait can be provided as an argument to
|
||||
/// [`Daemon::theme`].
|
||||
///
|
||||
/// `iced` provides two implementors:
|
||||
/// - the built-in [`Theme`] itself
|
||||
/// - and any `Fn(&State, window::Id) -> impl Into<Option<Theme>>`.
|
||||
pub trait ThemeFn<State, Theme> {
|
||||
/// Returns the theme of the [`Daemon`] for the current state and window.
|
||||
///
|
||||
/// If `None` is returned, `iced` will try to use a theme that
|
||||
/// matches the system color scheme.
|
||||
fn theme(&self, state: &State, window: window::Id) -> Option<Theme>;
|
||||
}
|
||||
|
||||
impl<State> ThemeFn<State, Theme> for Theme {
|
||||
fn theme(&self, _state: &State, _window: window::Id) -> Option<Theme> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, State, Theme> ThemeFn<State, Theme> for F
|
||||
where
|
||||
F: Fn(&State, window::Id) -> T,
|
||||
T: Into<Option<Theme>>,
|
||||
{
|
||||
fn theme(&self, state: &State, window: window::Id) -> Option<Theme> {
|
||||
(self)(state, window).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
src/lib.rs
13
src/lib.rs
|
|
@ -589,11 +589,12 @@ pub mod mouse {
|
|||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
pub mod system {
|
||||
//! Retrieve system information.
|
||||
pub use crate::runtime::system::Information;
|
||||
pub use crate::shell::system::*;
|
||||
pub use crate::runtime::system::{theme, theme_changes};
|
||||
|
||||
#[cfg(feature = "sysinfo")]
|
||||
pub use crate::runtime::system::{Information, information};
|
||||
}
|
||||
|
||||
pub mod overlay {
|
||||
|
|
@ -691,14 +692,14 @@ pub type Result = std::result::Result<(), Error>;
|
|||
/// }
|
||||
/// ```
|
||||
pub fn run<State, Message, Theme, Renderer>(
|
||||
update: impl application::Update<State, Message> + 'static,
|
||||
view: impl for<'a> application::View<'a, State, Message, Theme, Renderer>
|
||||
update: impl application::UpdateFn<State, Message> + 'static,
|
||||
view: impl for<'a> application::ViewFn<'a, State, Message, Theme, Renderer>
|
||||
+ 'static,
|
||||
) -> Result
|
||||
where
|
||||
State: Default + 'static,
|
||||
Message: Send + message::MaybeDebug + message::MaybeClone + 'static,
|
||||
Theme: Default + theme::Base + 'static,
|
||||
Theme: theme::Base + 'static,
|
||||
Renderer: program::Renderer + 'static,
|
||||
{
|
||||
application(State::default, update, view).run()
|
||||
|
|
|
|||
14
src/time.rs
14
src/time.rs
|
|
@ -11,3 +11,17 @@ pub use crate::core::time::*;
|
|||
)))
|
||||
)]
|
||||
pub use iced_futures::backend::default::time::*;
|
||||
|
||||
use crate::Task;
|
||||
|
||||
/// Returns a [`Task`] that produces the current [`Instant`]
|
||||
/// by calling [`Instant::now`].
|
||||
///
|
||||
/// While you can call [`Instant::now`] directly in your application;
|
||||
/// that renders your application "impure" (i.e. no referential transparency).
|
||||
///
|
||||
/// You may care about purity if you want to leverage the `time-travel`
|
||||
/// feature properly.
|
||||
pub fn now() -> Task<Instant> {
|
||||
Task::future(async { Instant::now() })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -379,7 +379,7 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
program.view(&self.state, self.window)
|
||||
}
|
||||
|
||||
pub fn theme(&self, program: &P) -> P::Theme {
|
||||
pub fn theme(&self, program: &P) -> Option<P::Theme> {
|
||||
program.theme(&self.state, self.window)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use crate::{Error, Selector};
|
|||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -235,7 +236,7 @@ where
|
|||
screenshot: window::Screenshot::new(
|
||||
rgba,
|
||||
physical_size,
|
||||
f64::from(scale_factor),
|
||||
scale_factor,
|
||||
),
|
||||
renderer: self.renderer.name(),
|
||||
})
|
||||
|
|
@ -267,10 +268,13 @@ impl Snapshot {
|
|||
|
||||
if path.exists() {
|
||||
let file = fs::File::open(&path)?;
|
||||
let decoder = png::Decoder::new(file);
|
||||
let decoder = png::Decoder::new(io::BufReader::new(file));
|
||||
|
||||
let mut reader = decoder.read_info()?;
|
||||
let mut bytes = vec![0; reader.output_buffer_size()];
|
||||
let n = reader
|
||||
.output_buffer_size()
|
||||
.expect("snapshot should fit in memory");
|
||||
let mut bytes = vec![0; n];
|
||||
let info = reader.next_frame(&mut bytes)?;
|
||||
|
||||
Ok(self.screenshot.bytes == bytes[..info.buffer_size()])
|
||||
|
|
|
|||
|
|
@ -566,12 +566,16 @@ impl<P: Program + 'static> Tester<P> {
|
|||
let viewport = container(
|
||||
scrollable(
|
||||
container(match &self.state {
|
||||
State::Empty => horizontal_space().into(),
|
||||
State::Idle { state } => Element::from(themer(
|
||||
program.theme(state, window),
|
||||
program.view(state, window),
|
||||
))
|
||||
.map(Tick::Program),
|
||||
State::Empty => Element::from(horizontal_space()),
|
||||
State::Idle { state } => {
|
||||
let theme = program.theme(state, window);
|
||||
|
||||
themer(
|
||||
theme,
|
||||
program.view(state, window).map(Tick::Program),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
State::Recording { emulator } => {
|
||||
let theme = emulator.theme(program);
|
||||
let view = emulator.view(program).map(Tick::Program);
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ impl Renderer {
|
|||
damage: &[Rectangle],
|
||||
background_color: Color,
|
||||
) {
|
||||
let scale_factor = viewport.scale_factor() as f32;
|
||||
let scale_factor = viewport.scale_factor();
|
||||
|
||||
self.layers.flush();
|
||||
|
||||
|
|
@ -404,8 +404,7 @@ impl renderer::Headless for Renderer {
|
|||
scale_factor: f32,
|
||||
background_color: Color,
|
||||
) -> Vec<u8> {
|
||||
let viewport =
|
||||
Viewport::with_physical_size(size, f64::from(scale_factor));
|
||||
let viewport = Viewport::with_physical_size(size, scale_factor);
|
||||
|
||||
window::compositor::screenshot(self, &viewport, background_color)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ impl crate::graphics::Compositor for Compositor {
|
|||
surface.layer_stack.clear();
|
||||
}
|
||||
|
||||
fn fetch_information(&self) -> Information {
|
||||
fn information(&self) -> Information {
|
||||
Information {
|
||||
adapter: String::from("CPU"),
|
||||
backend: String::from("tiny-skia"),
|
||||
|
|
|
|||
|
|
@ -235,13 +235,13 @@ impl Layer {
|
|||
pub fn draw_primitive(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
primitive: Box<dyn Primitive>,
|
||||
primitive: impl Primitive,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let bounds = bounds * transformation;
|
||||
|
||||
self.primitives
|
||||
.push(primitive::Instance { bounds, primitive });
|
||||
.push(primitive::Instance::new(bounds, primitive));
|
||||
}
|
||||
|
||||
fn flush_meshes(&mut self) {
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ impl Renderer {
|
|||
encoder: &mut wgpu::CommandEncoder,
|
||||
viewport: &Viewport,
|
||||
) {
|
||||
let scale_factor = viewport.scale_factor() as f32;
|
||||
let scale_factor = viewport.scale_factor();
|
||||
|
||||
self.text_viewport
|
||||
.update(&self.engine.queue, viewport.physical_size());
|
||||
|
|
@ -365,10 +365,10 @@ impl Renderer {
|
|||
|
||||
for instance in &layer.primitives {
|
||||
instance.primitive.prepare(
|
||||
&mut primitive_storage,
|
||||
&self.engine.device,
|
||||
&self.engine.queue,
|
||||
self.engine.format,
|
||||
&mut primitive_storage,
|
||||
&instance.bounds,
|
||||
viewport,
|
||||
);
|
||||
|
|
@ -464,7 +464,7 @@ impl Renderer {
|
|||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
let image_cache = self.image_cache.borrow();
|
||||
|
||||
let scale_factor = viewport.scale_factor() as f32;
|
||||
let scale_factor = viewport.scale_factor();
|
||||
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
|
||||
viewport.physical_size(),
|
||||
));
|
||||
|
|
@ -534,7 +534,6 @@ impl Renderer {
|
|||
|
||||
if !layer.primitives.is_empty() {
|
||||
let render_span = debug::render(debug::Primitive::Shader);
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
let primitive_storage = self
|
||||
.engine
|
||||
|
|
@ -542,21 +541,68 @@ impl Renderer {
|
|||
.read()
|
||||
.expect("Read primitive storage");
|
||||
|
||||
let mut need_render = Vec::new();
|
||||
|
||||
for instance in &layer.primitives {
|
||||
let bounds = instance.bounds * scale;
|
||||
|
||||
if let Some(clip_bounds) = (instance.bounds * scale)
|
||||
.intersection(&physical_bounds)
|
||||
.and_then(Rectangle::snap)
|
||||
{
|
||||
render_pass.set_viewport(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
clip_bounds.x,
|
||||
clip_bounds.y,
|
||||
clip_bounds.width,
|
||||
clip_bounds.height,
|
||||
);
|
||||
|
||||
let drawn = instance
|
||||
.primitive
|
||||
.draw(&primitive_storage, &mut render_pass);
|
||||
|
||||
if !drawn {
|
||||
need_render.push((instance, clip_bounds));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render_pass.set_viewport(
|
||||
0.0,
|
||||
0.0,
|
||||
viewport.physical_width() as f32,
|
||||
viewport.physical_height() as f32,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
0,
|
||||
0,
|
||||
viewport.physical_width(),
|
||||
viewport.physical_height(),
|
||||
);
|
||||
|
||||
if !need_render.is_empty() {
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
for (instance, clip_bounds) in need_render {
|
||||
instance.primitive.render(
|
||||
encoder,
|
||||
&primitive_storage,
|
||||
encoder,
|
||||
frame,
|
||||
&clip_bounds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render_span.finish();
|
||||
|
||||
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
|
|
@ -579,6 +625,9 @@ impl Renderer {
|
|||
));
|
||||
}
|
||||
|
||||
render_span.finish();
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
if !layer.images.is_empty() {
|
||||
let render_span = debug::render(debug::Primitive::Image);
|
||||
|
|
@ -804,7 +853,7 @@ impl graphics::geometry::Renderer for Renderer {
|
|||
impl primitive::Renderer for Renderer {
|
||||
fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_primitive(bounds, Box::new(primitive), transformation);
|
||||
layer.draw_primitive(bounds, primitive, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -878,7 +927,7 @@ impl renderer::Headless for Renderer {
|
|||
background_color: Color,
|
||||
) -> Vec<u8> {
|
||||
self.screenshot(
|
||||
&Viewport::with_physical_size(size, f64::from(scale_factor)),
|
||||
&Viewport::with_physical_size(size, scale_factor),
|
||||
background_color,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Draw custom primitives.
|
||||
use crate::core::{self, Rectangle};
|
||||
use crate::graphics::Viewport;
|
||||
use crate::graphics::futures::{MaybeSend, MaybeSync};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::any::{Any, TypeId};
|
||||
|
|
@ -10,36 +11,172 @@ use std::fmt::Debug;
|
|||
pub type Batch = Vec<Instance>;
|
||||
|
||||
/// A set of methods which allows a [`Primitive`] to be rendered.
|
||||
pub trait Primitive: Debug + Send + Sync + 'static {
|
||||
/// Processes the [`Primitive`], allowing for GPU buffer allocation.
|
||||
fn prepare(
|
||||
pub trait Primitive: Debug + MaybeSend + MaybeSync + 'static {
|
||||
/// The shared renderer of this [`Primitive`].
|
||||
///
|
||||
/// Normally, this will contain a bunch of [`wgpu`] state; like
|
||||
/// a rendering pipeline, buffers, and textures.
|
||||
///
|
||||
/// All instances of this [`Primitive`] type will share the same
|
||||
/// [`Renderer`].
|
||||
type Renderer: MaybeSend + MaybeSync;
|
||||
|
||||
/// Initializes the [`Renderer`](Self::Renderer) of the [`Primitive`].
|
||||
///
|
||||
/// This will only be called once, when the first [`Primitive`] of this kind
|
||||
/// is encountered.
|
||||
fn initialize(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
storage: &mut Storage,
|
||||
) -> Self::Renderer;
|
||||
|
||||
/// Processes the [`Primitive`], allowing for GPU buffer allocation.
|
||||
fn prepare(
|
||||
&self,
|
||||
renderer: &mut Self::Renderer,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
bounds: &Rectangle,
|
||||
viewport: &Viewport,
|
||||
);
|
||||
|
||||
/// Renders the [`Primitive`].
|
||||
/// Draws the [`Primitive`] in the given [`wgpu::RenderPass`].
|
||||
///
|
||||
/// When possible, this should be implemented over [`render`](Self::render)
|
||||
/// since reusing the existing render pass should be considerably more
|
||||
/// efficient than issuing a new one.
|
||||
///
|
||||
/// The viewport and scissor rect of the render pass provided is set
|
||||
/// to the bounds and clip bounds of the [`Primitive`], respectively.
|
||||
///
|
||||
/// If you have complex composition needs, then you can leverage
|
||||
/// [`render`](Self::render) by returning `false` here.
|
||||
///
|
||||
/// By default, it does nothing and returns `false`.
|
||||
fn draw(
|
||||
&self,
|
||||
_renderer: &Self::Renderer,
|
||||
_render_pass: &mut wgpu::RenderPass<'_>,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Renders the [`Primitive`], using the given [`wgpu::CommandEncoder`].
|
||||
///
|
||||
/// This will only be called if [`draw`](Self::draw) returns `false`.
|
||||
///
|
||||
/// By default, it does nothing.
|
||||
fn render(
|
||||
&self,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
_renderer: &Self::Renderer,
|
||||
_encoder: &mut wgpu::CommandEncoder,
|
||||
_target: &wgpu::TextureView,
|
||||
_clip_bounds: &Rectangle<u32>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait Stored:
|
||||
Debug + MaybeSend + MaybeSync + 'static
|
||||
{
|
||||
fn prepare(
|
||||
&self,
|
||||
storage: &mut Storage,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
bounds: &Rectangle,
|
||||
viewport: &Viewport,
|
||||
);
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
storage: &Storage,
|
||||
render_pass: &mut wgpu::RenderPass<'_>,
|
||||
) -> bool;
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
storage: &Storage,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
clip_bounds: &Rectangle<u32>,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BlackBox<P: Primitive> {
|
||||
primitive: P,
|
||||
}
|
||||
|
||||
impl<P: Primitive> Stored for BlackBox<P> {
|
||||
fn prepare(
|
||||
&self,
|
||||
storage: &mut Storage,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
bounds: &Rectangle,
|
||||
viewport: &Viewport,
|
||||
) {
|
||||
if !storage.has::<P>() {
|
||||
storage.store::<P, _>(
|
||||
self.primitive.initialize(device, queue, format),
|
||||
);
|
||||
}
|
||||
|
||||
let renderer = storage
|
||||
.get_mut::<P>()
|
||||
.expect("renderer should be initialized")
|
||||
.downcast_mut::<P::Renderer>()
|
||||
.expect("renderer should have the proper type");
|
||||
|
||||
self.primitive
|
||||
.prepare(renderer, device, queue, bounds, viewport);
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
storage: &Storage,
|
||||
render_pass: &mut wgpu::RenderPass<'_>,
|
||||
) -> bool {
|
||||
let renderer = storage
|
||||
.get::<P>()
|
||||
.expect("renderer should be initialized")
|
||||
.downcast_ref::<P::Renderer>()
|
||||
.expect("renderer should have the proper type");
|
||||
|
||||
self.primitive.draw(renderer, render_pass)
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
storage: &Storage,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
clip_bounds: &Rectangle<u32>,
|
||||
) {
|
||||
let renderer = storage
|
||||
.get::<P>()
|
||||
.expect("renderer should be initialized")
|
||||
.downcast_ref::<P::Renderer>()
|
||||
.expect("renderer should have the proper type");
|
||||
|
||||
self.primitive
|
||||
.render(renderer, encoder, target, clip_bounds);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// An instance of a specific [`Primitive`].
|
||||
pub struct Instance {
|
||||
/// The bounds of the [`Instance`].
|
||||
pub bounds: Rectangle,
|
||||
pub(crate) bounds: Rectangle,
|
||||
|
||||
/// The [`Primitive`] to render.
|
||||
pub primitive: Box<dyn Primitive>,
|
||||
pub(crate) primitive: Box<dyn Stored>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
|
|
@ -47,7 +184,7 @@ impl Instance {
|
|||
pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self {
|
||||
Instance {
|
||||
bounds,
|
||||
primitive: Box::new(primitive),
|
||||
primitive: Box::new(BlackBox { primitive }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,9 +196,10 @@ pub trait Renderer: core::Renderer {
|
|||
}
|
||||
|
||||
/// Stores custom, user-provided types.
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Storage {
|
||||
pipelines: FxHashMap<TypeId, Box<dyn Any + Send + Sync>>,
|
||||
pipelines: FxHashMap<TypeId, Box<dyn AnyConcurrent>>,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
|
|
@ -71,25 +209,28 @@ impl Storage {
|
|||
}
|
||||
|
||||
/// Inserts the data `T` in to [`Storage`].
|
||||
pub fn store<T: 'static + Send + Sync>(&mut self, data: T) {
|
||||
pub fn store<T: 'static, D: Any + MaybeSend + MaybeSync>(
|
||||
&mut self,
|
||||
data: D,
|
||||
) {
|
||||
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(data));
|
||||
}
|
||||
|
||||
/// Returns a reference to the data with type `T` if it exists in [`Storage`].
|
||||
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||
self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| {
|
||||
pipeline
|
||||
.downcast_ref::<T>()
|
||||
.expect("Value with this type does not exist in Storage.")
|
||||
})
|
||||
pub fn get<T: 'static>(&self) -> Option<&dyn Any> {
|
||||
self.pipelines
|
||||
.get(&TypeId::of::<T>())
|
||||
.map(|pipeline| pipeline.as_ref() as &dyn Any)
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the data with type `T` if it exists in [`Storage`].
|
||||
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||
self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| {
|
||||
pipeline
|
||||
.downcast_mut::<T>()
|
||||
.expect("Value with this type does not exist in Storage.")
|
||||
})
|
||||
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut dyn Any> {
|
||||
self.pipelines
|
||||
.get_mut(&TypeId::of::<T>())
|
||||
.map(|pipeline| pipeline.as_mut() as &mut dyn Any)
|
||||
}
|
||||
}
|
||||
|
||||
trait AnyConcurrent: Any + MaybeSend + MaybeSync {}
|
||||
|
||||
impl<T> AnyConcurrent for T where T: Any + MaybeSend + MaybeSync {}
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ impl graphics::Compositor for Compositor {
|
|||
);
|
||||
}
|
||||
|
||||
fn fetch_information(&self) -> compositor::Information {
|
||||
fn information(&self) -> compositor::Information {
|
||||
let information = self.adapter.get_info();
|
||||
|
||||
compositor::Information {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use crate::checkbox::{self, Checkbox};
|
|||
use crate::combo_box::{self, ComboBox};
|
||||
use crate::container::{self, Container};
|
||||
use crate::core;
|
||||
use crate::core::theme;
|
||||
use crate::core::widget::operation::{self, Operation};
|
||||
use crate::core::window;
|
||||
use crate::core::{Element, Length, Pixels, Size, Widget};
|
||||
|
|
@ -2050,10 +2051,11 @@ where
|
|||
|
||||
/// A widget that applies any `Theme` to its contents.
|
||||
pub fn themer<'a, Message, Theme, Renderer>(
|
||||
theme: Theme,
|
||||
theme: Option<Theme>,
|
||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||
) -> Themer<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Theme: theme::Base,
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
Themer::new(theme, content)
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ where
|
|||
padding: Padding::ZERO,
|
||||
text_size: None,
|
||||
text_line_height: text::LineHeight::default(),
|
||||
text_shaping: text::Shaping::Basic,
|
||||
text_shaping: text::Shaping::default(),
|
||||
font: None,
|
||||
class,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use crate::core::layout;
|
|||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
use crate::core::renderer;
|
||||
use crate::core::theme;
|
||||
use crate::core::widget::Operation;
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::{
|
||||
|
|
@ -20,7 +21,7 @@ where
|
|||
Renderer: crate::core::Renderer,
|
||||
{
|
||||
content: Element<'a, Message, Theme, Renderer>,
|
||||
theme: Theme,
|
||||
theme: Option<Theme>,
|
||||
text_color: Option<fn(&Theme) -> Color>,
|
||||
background: Option<fn(&Theme) -> Background>,
|
||||
}
|
||||
|
|
@ -32,7 +33,7 @@ where
|
|||
/// Creates an empty [`Themer`] that applies the given `Theme`
|
||||
/// to the provided `content`.
|
||||
pub fn new(
|
||||
theme: Theme,
|
||||
theme: Option<Theme>,
|
||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
|
@ -59,6 +60,8 @@ where
|
|||
impl<Message, Theme, Renderer, AnyTheme> Widget<Message, AnyTheme, Renderer>
|
||||
for Themer<'_, Message, Theme, Renderer>
|
||||
where
|
||||
Theme: theme::Base,
|
||||
AnyTheme: theme::Base,
|
||||
Renderer: crate::core::Renderer,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
|
|
@ -135,17 +138,20 @@ where
|
|||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
_theme: &AnyTheme,
|
||||
theme: &AnyTheme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let default_theme = theme::Base::default(theme.mode());
|
||||
let theme = self.theme.as_ref().unwrap_or(&default_theme);
|
||||
|
||||
if let Some(background) = self.background {
|
||||
container::draw_background(
|
||||
renderer,
|
||||
&container::Style {
|
||||
background: Some(background(&self.theme)),
|
||||
background: Some(background(theme)),
|
||||
..container::Style::default()
|
||||
},
|
||||
layout.bounds(),
|
||||
|
|
@ -154,21 +160,15 @@ where
|
|||
|
||||
let style = if let Some(text_color) = self.text_color {
|
||||
renderer::Style {
|
||||
text_color: text_color(&self.theme),
|
||||
text_color: text_color(theme),
|
||||
}
|
||||
} else {
|
||||
*style
|
||||
};
|
||||
|
||||
self.content.as_widget().draw(
|
||||
tree,
|
||||
renderer,
|
||||
&self.theme,
|
||||
&style,
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
self.content
|
||||
.as_widget()
|
||||
.draw(tree, renderer, theme, &style, layout, cursor, viewport);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
|
|
@ -180,7 +180,7 @@ where
|
|||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, AnyTheme, Renderer>> {
|
||||
struct Overlay<'a, Message, Theme, Renderer> {
|
||||
theme: &'a Theme,
|
||||
theme: &'a Option<Theme>,
|
||||
content: overlay::Element<'a, Message, Theme, Renderer>,
|
||||
}
|
||||
|
||||
|
|
@ -188,6 +188,8 @@ where
|
|||
overlay::Overlay<Message, AnyTheme, Renderer>
|
||||
for Overlay<'_, Message, Theme, Renderer>
|
||||
where
|
||||
Theme: theme::Base,
|
||||
AnyTheme: theme::Base,
|
||||
Renderer: crate::core::Renderer,
|
||||
{
|
||||
fn layout(
|
||||
|
|
@ -201,14 +203,17 @@ where
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_theme: &AnyTheme,
|
||||
theme: &AnyTheme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
) {
|
||||
let default_theme = theme::Base::default(theme.mode());
|
||||
let theme = self.theme.as_ref().unwrap_or(&default_theme);
|
||||
|
||||
self.content
|
||||
.as_overlay()
|
||||
.draw(renderer, self.theme, style, layout, cursor);
|
||||
.draw(renderer, theme, style, layout, cursor);
|
||||
}
|
||||
|
||||
fn update(
|
||||
|
|
@ -280,7 +285,8 @@ impl<'a, Message, Theme, Renderer, AnyTheme>
|
|||
for Element<'a, Message, AnyTheme, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Theme: 'a,
|
||||
Theme: theme::Base + 'a,
|
||||
AnyTheme: theme::Base,
|
||||
Renderer: 'a + crate::core::Renderer,
|
||||
{
|
||||
fn from(
|
||||
|
|
|
|||
|
|
@ -214,11 +214,9 @@ where
|
|||
|
||||
let is_idle = *state == State::Idle;
|
||||
|
||||
if was_idle != is_idle {
|
||||
shell.invalidate_layout();
|
||||
shell.request_redraw();
|
||||
} else if self.position == Position::FollowCursor
|
||||
&& previous_state != *state
|
||||
if was_idle != is_idle
|
||||
|| (self.position == Position::FollowCursor
|
||||
&& previous_state != *state)
|
||||
{
|
||||
shell.request_redraw();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,13 @@ workspace = true
|
|||
[features]
|
||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
debug = ["iced_debug/enable"]
|
||||
system = ["sysinfo"]
|
||||
sysinfo = ["dep:sysinfo"]
|
||||
x11 = ["winit/x11"]
|
||||
wayland = ["winit/wayland"]
|
||||
wayland-dlopen = ["winit/wayland-dlopen"]
|
||||
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
||||
unconditional-rendering = []
|
||||
linux-theme-detection = ["dep:mundy", "mundy/async-io", "mundy/color-scheme"]
|
||||
|
||||
[dependencies]
|
||||
iced_debug.workspace = true
|
||||
|
|
@ -41,3 +42,7 @@ sysinfo.optional = true
|
|||
web-sys.workspace = true
|
||||
web-sys.features = ["Document", "Window", "HtmlCanvasElement"]
|
||||
wasm-bindgen-futures.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
mundy.workspace = true
|
||||
mundy.optional = true
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
use crate::core::input_method;
|
||||
use crate::core::keyboard;
|
||||
use crate::core::mouse;
|
||||
use crate::core::theme;
|
||||
use crate::core::touch;
|
||||
use crate::core::window;
|
||||
use crate::core::{Event, Point, Size};
|
||||
|
|
@ -13,6 +14,7 @@ use crate::core::{Event, Point, Size};
|
|||
pub fn window_attributes(
|
||||
settings: window::Settings,
|
||||
title: &str,
|
||||
scale_factor: f32,
|
||||
primary_monitor: Option<winit::monitor::MonitorHandle>,
|
||||
_id: Option<String>,
|
||||
) -> winit::window::WindowAttributes {
|
||||
|
|
@ -21,8 +23,8 @@ pub fn window_attributes(
|
|||
attributes = attributes
|
||||
.with_title(title)
|
||||
.with_inner_size(winit::dpi::LogicalSize {
|
||||
width: settings.size.width,
|
||||
height: settings.size.height,
|
||||
width: settings.size.width * scale_factor,
|
||||
height: settings.size.height * scale_factor,
|
||||
})
|
||||
.with_maximized(settings.maximized)
|
||||
.with_fullscreen(
|
||||
|
|
@ -138,7 +140,7 @@ pub fn window_attributes(
|
|||
/// Converts a winit window event into an iced event.
|
||||
pub fn window_event(
|
||||
event: winit::event::WindowEvent,
|
||||
scale_factor: f64,
|
||||
scale_factor: f32,
|
||||
modifiers: winit::keyboard::ModifiersState,
|
||||
) -> Option<Event> {
|
||||
use winit::event::Ime;
|
||||
|
|
@ -146,7 +148,7 @@ pub fn window_event(
|
|||
|
||||
match event {
|
||||
WindowEvent::Resized(new_size) => {
|
||||
let logical_size = new_size.to_logical(scale_factor);
|
||||
let logical_size = new_size.to_logical(f64::from(scale_factor));
|
||||
|
||||
Some(Event::Window(window::Event::Resized(Size {
|
||||
width: logical_size.width,
|
||||
|
|
@ -157,7 +159,7 @@ pub fn window_event(
|
|||
Some(Event::Window(window::Event::CloseRequested))
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
let position = position.to_logical::<f64>(scale_factor);
|
||||
let position = position.to_logical::<f64>(f64::from(scale_factor));
|
||||
|
||||
Some(Event::Mouse(mouse::Event::CursorMoved {
|
||||
position: Point::new(position.x as f32, position.y as f32),
|
||||
|
|
@ -313,7 +315,7 @@ pub fn window_event(
|
|||
}
|
||||
WindowEvent::Moved(position) => {
|
||||
let winit::dpi::LogicalPosition { x, y } =
|
||||
position.to_logical(scale_factor);
|
||||
position.to_logical(f64::from(scale_factor));
|
||||
|
||||
Some(Event::Window(window::Event::Moved(Point::new(x, y))))
|
||||
}
|
||||
|
|
@ -321,7 +323,7 @@ pub fn window_event(
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`window::Level`] to a [`winit`] window level.
|
||||
/// Converts a [`window::Level`] into a [`winit`] window level.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn window_level(level: window::Level) -> winit::window::WindowLevel {
|
||||
|
|
@ -334,7 +336,7 @@ pub fn window_level(level: window::Level) -> winit::window::WindowLevel {
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`window::Position`] to a [`winit`] logical position for a given monitor.
|
||||
/// Converts a [`window::Position`] into a [`winit`] logical position for a given monitor.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn position(
|
||||
|
|
@ -406,7 +408,7 @@ pub fn position(
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`window::Mode`] to a [`winit`] fullscreen mode.
|
||||
/// Converts a [`window::Mode`] into a [`winit`] fullscreen mode.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn fullscreen(
|
||||
|
|
@ -421,7 +423,7 @@ pub fn fullscreen(
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`window::Mode`] to a visibility flag.
|
||||
/// Converts a [`window::Mode`] into a visibility flag.
|
||||
pub fn visible(mode: window::Mode) -> bool {
|
||||
match mode {
|
||||
window::Mode::Windowed | window::Mode::Fullscreen => true,
|
||||
|
|
@ -429,7 +431,7 @@ pub fn visible(mode: window::Mode) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`winit`] fullscreen mode to a [`window::Mode`].
|
||||
/// Converts a [`winit`] fullscreen mode into a [`window::Mode`].
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn mode(mode: Option<winit::window::Fullscreen>) -> window::Mode {
|
||||
|
|
@ -439,7 +441,28 @@ pub fn mode(mode: Option<winit::window::Fullscreen>) -> window::Mode {
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a [`mouse::Interaction`] to a [`winit`] cursor icon.
|
||||
/// Converts a [`winit`] window theme into a [`theme::Mode`].
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn theme_mode(theme: winit::window::Theme) -> theme::Mode {
|
||||
match theme {
|
||||
winit::window::Theme::Light => theme::Mode::Light,
|
||||
winit::window::Theme::Dark => theme::Mode::Dark,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`theme::Mode`] into a window theme.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn window_theme(mode: theme::Mode) -> Option<winit::window::Theme> {
|
||||
match mode {
|
||||
theme::Mode::None => None,
|
||||
theme::Mode::Light => Some(winit::window::Theme::Light),
|
||||
theme::Mode::Dark => Some(winit::window::Theme::Dark),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`mouse::Interaction`] into a [`winit`] cursor icon.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
pub fn mouse_interaction(
|
||||
|
|
@ -510,12 +533,12 @@ pub fn modifiers(
|
|||
result
|
||||
}
|
||||
|
||||
/// Converts a physical cursor position to a logical `Point`.
|
||||
/// Converts a physical cursor position into a logical `Point`.
|
||||
pub fn cursor_position(
|
||||
position: winit::dpi::PhysicalPosition<f64>,
|
||||
scale_factor: f64,
|
||||
scale_factor: f32,
|
||||
) -> Point {
|
||||
let logical_position = position.to_logical(scale_factor);
|
||||
let logical_position = position.to_logical(f64::from(scale_factor));
|
||||
|
||||
Point::new(logical_position.x, logical_position.y)
|
||||
}
|
||||
|
|
@ -526,11 +549,12 @@ pub fn cursor_position(
|
|||
/// [`iced`]: https://github.com/iced-rs/iced/tree/0.12
|
||||
pub fn touch_event(
|
||||
touch: winit::event::Touch,
|
||||
scale_factor: f64,
|
||||
scale_factor: f32,
|
||||
) -> touch::Event {
|
||||
let id = touch::Finger(touch.id);
|
||||
let position = {
|
||||
let location = touch.location.to_logical::<f64>(scale_factor);
|
||||
let location =
|
||||
touch.location.to_logical::<f64>(f64::from(scale_factor));
|
||||
|
||||
Point::new(location.x as f32, location.y as f32)
|
||||
};
|
||||
|
|
@ -1180,7 +1204,7 @@ pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {
|
|||
winit::window::Icon::from_rgba(pixels, size.width, size.height).ok()
|
||||
}
|
||||
|
||||
/// Convertions some [`input_method::Purpose`] to its `winit` counterpart.
|
||||
/// Converts some [`input_method::Purpose`] into its `winit` counterpart.
|
||||
pub fn ime_purpose(
|
||||
purpose: input_method::Purpose,
|
||||
) -> winit::window::ImePurpose {
|
||||
|
|
|
|||
214
winit/src/lib.rs
214
winit/src/lib.rs
|
|
@ -29,9 +29,6 @@ pub use winit;
|
|||
pub mod clipboard;
|
||||
pub mod conversion;
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
pub mod system;
|
||||
|
||||
mod error;
|
||||
mod proxy;
|
||||
mod window;
|
||||
|
|
@ -53,6 +50,7 @@ use crate::futures::futures::{Future, StreamExt};
|
|||
use crate::futures::subscription;
|
||||
use crate::futures::{Executor, Runtime};
|
||||
use crate::graphics::{Compositor, compositor};
|
||||
use crate::runtime::system;
|
||||
use crate::runtime::user_interface::{self, UserInterface};
|
||||
use crate::runtime::{Action, Task};
|
||||
|
||||
|
|
@ -109,7 +107,7 @@ where
|
|||
|
||||
let (_id, open) = runtime::window::open(window_settings);
|
||||
|
||||
open.then(move |_| task.take().unwrap_or(Task::none()))
|
||||
open.then(move |_| task.take().unwrap_or_else(Task::none))
|
||||
} else {
|
||||
task
|
||||
};
|
||||
|
|
@ -124,6 +122,7 @@ where
|
|||
|
||||
let (event_sender, event_receiver) = mpsc::unbounded();
|
||||
let (control_sender, control_receiver) = mpsc::unbounded();
|
||||
let (system_theme_sender, system_theme_receiver) = oneshot::channel();
|
||||
|
||||
let instance = Box::pin(run_instance::<P>(
|
||||
program,
|
||||
|
|
@ -134,6 +133,7 @@ where
|
|||
is_daemon,
|
||||
graphics_settings,
|
||||
settings.fonts,
|
||||
system_theme_receiver,
|
||||
));
|
||||
|
||||
let context = task::Context::from_waker(task::noop_waker_ref());
|
||||
|
|
@ -145,6 +145,7 @@ where
|
|||
sender: mpsc::UnboundedSender<Event<Action<Message>>>,
|
||||
receiver: mpsc::UnboundedReceiver<Control>,
|
||||
error: Option<Error>,
|
||||
system_theme: Option<oneshot::Sender<theme::Mode>>,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
canvas: Option<web_sys::HtmlCanvasElement>,
|
||||
|
|
@ -157,6 +158,7 @@ where
|
|||
sender: event_sender,
|
||||
receiver: control_receiver,
|
||||
error: None,
|
||||
system_theme: Some(system_theme_sender),
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
canvas: None,
|
||||
|
|
@ -169,10 +171,15 @@ where
|
|||
where
|
||||
F: Future<Output = ()>,
|
||||
{
|
||||
fn resumed(
|
||||
&mut self,
|
||||
_event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
) {
|
||||
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
if let Some(sender) = self.system_theme.take() {
|
||||
let _ = sender.send(
|
||||
event_loop
|
||||
.system_theme()
|
||||
.map(conversion::theme_mode)
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn new_events(
|
||||
|
|
@ -307,6 +314,7 @@ where
|
|||
id,
|
||||
settings,
|
||||
title,
|
||||
scale_factor,
|
||||
monitor,
|
||||
on_open,
|
||||
} => {
|
||||
|
|
@ -323,6 +331,7 @@ where
|
|||
conversion::window_attributes(
|
||||
settings,
|
||||
&title,
|
||||
scale_factor,
|
||||
monitor
|
||||
.or(event_loop.primary_monitor()),
|
||||
self.id.clone(),
|
||||
|
|
@ -417,7 +426,9 @@ where
|
|||
);
|
||||
}
|
||||
Control::Exit => {
|
||||
self.process_event(event_loop, Event::Exit);
|
||||
event_loop.exit();
|
||||
break;
|
||||
}
|
||||
Control::Crash(error) => {
|
||||
self.error = Some(error);
|
||||
|
|
@ -464,6 +475,7 @@ enum Event<Message: 'static> {
|
|||
on_open: oneshot::Sender<window::Id>,
|
||||
},
|
||||
EventLoopAwakened(winit::event::Event<Message>),
|
||||
Exit,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -477,6 +489,7 @@ enum Control {
|
|||
title: String,
|
||||
monitor: Option<winit::monitor::MonitorHandle>,
|
||||
on_open: oneshot::Sender<window::Id>,
|
||||
scale_factor: f32,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -489,6 +502,7 @@ async fn run_instance<P>(
|
|||
is_daemon: bool,
|
||||
graphics_settings: graphics::Settings,
|
||||
default_fonts: Vec<Cow<'static, [u8]>>,
|
||||
mut _system_theme: oneshot::Receiver<theme::Mode>,
|
||||
) where
|
||||
P: Program + 'static,
|
||||
P::Theme: theme::Base,
|
||||
|
|
@ -508,6 +522,38 @@ async fn run_instance<P>(
|
|||
let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
|
||||
let mut clipboard = Clipboard::unconnected();
|
||||
|
||||
#[cfg(all(feature = "linux-theme-detection", target_os = "linux"))]
|
||||
let mut system_theme = {
|
||||
let to_mode = |color_scheme| match color_scheme {
|
||||
mundy::ColorScheme::NoPreference => theme::Mode::None,
|
||||
mundy::ColorScheme::Light => theme::Mode::Light,
|
||||
mundy::ColorScheme::Dark => theme::Mode::Dark,
|
||||
};
|
||||
|
||||
runtime.run(
|
||||
mundy::Preferences::stream(mundy::Interest::ColorScheme)
|
||||
.map(move |preferences| {
|
||||
Action::System(system::Action::NotifyTheme(to_mode(
|
||||
preferences.color_scheme,
|
||||
)))
|
||||
})
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
mundy::Preferences::once_blocking(
|
||||
mundy::Interest::ColorScheme,
|
||||
core::time::Duration::from_millis(200),
|
||||
)
|
||||
.map(|preferences| to_mode(preferences.color_scheme))
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
#[cfg(not(all(feature = "linux-theme-detection", target_os = "linux")))]
|
||||
let mut system_theme =
|
||||
_system_theme.try_recv().ok().flatten().unwrap_or_default();
|
||||
|
||||
log::info!("System theme: {system_theme:?}");
|
||||
|
||||
loop {
|
||||
// Empty the queue if possible
|
||||
let event = if let Ok(event) = event_receiver.try_next() {
|
||||
|
|
@ -586,14 +632,20 @@ async fn run_instance<P>(
|
|||
}
|
||||
}
|
||||
|
||||
debug::theme_changed(|| {
|
||||
if window_manager.is_empty() {
|
||||
theme::Base::palette(&program.theme(id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let window_theme = window
|
||||
.theme()
|
||||
.map(conversion::theme_mode)
|
||||
.unwrap_or_default();
|
||||
|
||||
if system_theme != window_theme {
|
||||
system_theme = window_theme;
|
||||
|
||||
runtime.broadcast(subscription::Event::SystemThemeChanged(
|
||||
window_theme,
|
||||
));
|
||||
}
|
||||
|
||||
let is_first = window_manager.is_empty();
|
||||
let window = window_manager.insert(
|
||||
id,
|
||||
window,
|
||||
|
|
@ -602,8 +654,21 @@ async fn run_instance<P>(
|
|||
.as_mut()
|
||||
.expect("Compositor must be initialized"),
|
||||
exit_on_close_request,
|
||||
system_theme,
|
||||
);
|
||||
|
||||
window.raw.set_theme(conversion::window_theme(
|
||||
window.state.theme_mode(),
|
||||
));
|
||||
|
||||
debug::theme_changed(|| {
|
||||
if is_first {
|
||||
theme::Base::palette(window.state.theme())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let logical_size = window.state.logical_size();
|
||||
|
||||
let _ = user_interfaces.insert(
|
||||
|
|
@ -686,6 +751,7 @@ async fn run_instance<P>(
|
|||
run_action(
|
||||
action,
|
||||
&program,
|
||||
&mut runtime,
|
||||
&mut compositor,
|
||||
&mut events,
|
||||
&mut messages,
|
||||
|
|
@ -695,6 +761,7 @@ async fn run_instance<P>(
|
|||
&mut window_manager,
|
||||
&mut ui_caches,
|
||||
&mut is_window_opening,
|
||||
&mut system_theme,
|
||||
);
|
||||
actions += 1;
|
||||
}
|
||||
|
|
@ -853,12 +920,25 @@ async fn run_instance<P>(
|
|||
continue;
|
||||
};
|
||||
|
||||
if matches!(
|
||||
window_event,
|
||||
winit::event::WindowEvent::Resized(_)
|
||||
) {
|
||||
match window_event {
|
||||
winit::event::WindowEvent::Resized(_) => {
|
||||
window.raw.request_redraw();
|
||||
}
|
||||
winit::event::WindowEvent::ThemeChanged(theme) => {
|
||||
let mode = conversion::theme_mode(theme);
|
||||
|
||||
if mode != system_theme {
|
||||
system_theme = mode;
|
||||
|
||||
runtime.broadcast(
|
||||
subscription::Event::SystemThemeChanged(
|
||||
mode,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if matches!(
|
||||
window_event,
|
||||
|
|
@ -870,6 +950,7 @@ async fn run_instance<P>(
|
|||
id,
|
||||
)),
|
||||
&program,
|
||||
&mut runtime,
|
||||
&mut compositor,
|
||||
&mut events,
|
||||
&mut messages,
|
||||
|
|
@ -879,9 +960,14 @@ async fn run_instance<P>(
|
|||
&mut window_manager,
|
||||
&mut ui_caches,
|
||||
&mut is_window_opening,
|
||||
&mut system_theme,
|
||||
);
|
||||
} else {
|
||||
window.state.update(&window.raw, &window_event);
|
||||
window.state.update(
|
||||
&program,
|
||||
&window.raw,
|
||||
&window_event,
|
||||
);
|
||||
|
||||
if let Some(event) = conversion::window_event(
|
||||
window_event,
|
||||
|
|
@ -1033,6 +1119,7 @@ async fn run_instance<P>(
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
Event::Exit => break,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1085,6 +1172,7 @@ fn update<P: Program, E: Executor>(
|
|||
fn run_action<'a, P, C>(
|
||||
action: Action<P::Message>,
|
||||
program: &'a program::Instance<P>,
|
||||
runtime: &mut Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
|
||||
compositor: &mut Option<C>,
|
||||
events: &mut Vec<(window::Id, core::Event)>,
|
||||
messages: &mut Vec<P::Message>,
|
||||
|
|
@ -1097,13 +1185,13 @@ fn run_action<'a, P, C>(
|
|||
window_manager: &mut WindowManager<P, C>,
|
||||
ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>,
|
||||
is_window_opening: &mut bool,
|
||||
system_theme: &mut theme::Mode,
|
||||
) where
|
||||
P: Program,
|
||||
C: Compositor<Renderer = P::Renderer> + 'static,
|
||||
P::Theme: theme::Base,
|
||||
{
|
||||
use crate::runtime::clipboard;
|
||||
use crate::runtime::system;
|
||||
use crate::runtime::window;
|
||||
|
||||
match action {
|
||||
|
|
@ -1127,6 +1215,7 @@ fn run_action<'a, P, C>(
|
|||
id,
|
||||
settings,
|
||||
title: program.title(id),
|
||||
scale_factor: program.scale_factor(id),
|
||||
monitor,
|
||||
on_open: channel,
|
||||
})
|
||||
|
|
@ -1187,7 +1276,10 @@ fn run_action<'a, P, C>(
|
|||
winit::dpi::LogicalSize {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
},
|
||||
}
|
||||
.to_physical::<f32>(f64::from(
|
||||
window.state.scale_factor(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1198,6 +1290,9 @@ fn run_action<'a, P, C>(
|
|||
width: size.width,
|
||||
height: size.height,
|
||||
}
|
||||
.to_physical::<f32>(f64::from(
|
||||
window.state.scale_factor(),
|
||||
))
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -1208,6 +1303,9 @@ fn run_action<'a, P, C>(
|
|||
width: size.width,
|
||||
height: size.height,
|
||||
}
|
||||
.to_physical::<f32>(f64::from(
|
||||
window.state.scale_factor(),
|
||||
))
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -1218,6 +1316,9 @@ fn run_action<'a, P, C>(
|
|||
width: size.width,
|
||||
height: size.height,
|
||||
}
|
||||
.to_physical::<f32>(f64::from(
|
||||
window.state.scale_factor(),
|
||||
))
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -1231,7 +1332,7 @@ fn run_action<'a, P, C>(
|
|||
let size = window
|
||||
.raw
|
||||
.inner_size()
|
||||
.to_logical(window.raw.scale_factor());
|
||||
.to_logical(f64::from(window.state.scale_factor()));
|
||||
|
||||
let _ = channel.send(Size::new(size.width, size.height));
|
||||
}
|
||||
|
|
@ -1398,21 +1499,44 @@ fn run_action<'a, P, C>(
|
|||
}
|
||||
},
|
||||
Action::System(action) => match action {
|
||||
system::Action::QueryInformation(_channel) => {
|
||||
#[cfg(feature = "system")]
|
||||
system::Action::GetInformation(_channel) => {
|
||||
#[cfg(feature = "sysinfo")]
|
||||
{
|
||||
if let Some(compositor) = compositor {
|
||||
let graphics_info = compositor.fetch_information();
|
||||
let graphics_info = compositor.information();
|
||||
|
||||
let _ = std::thread::spawn(move || {
|
||||
let information =
|
||||
crate::system::information(graphics_info);
|
||||
let information = system_information(graphics_info);
|
||||
|
||||
let _ = _channel.send(information);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
system::Action::GetTheme(channel) => {
|
||||
let _ = channel.send(*system_theme);
|
||||
}
|
||||
system::Action::NotifyTheme(mode) => {
|
||||
if mode != *system_theme {
|
||||
*system_theme = mode;
|
||||
|
||||
runtime.broadcast(subscription::Event::SystemThemeChanged(
|
||||
mode,
|
||||
));
|
||||
}
|
||||
|
||||
let Some(theme) = conversion::window_theme(mode) else {
|
||||
return;
|
||||
};
|
||||
|
||||
for (_id, window) in window_manager.iter_mut() {
|
||||
window.state.update(
|
||||
program,
|
||||
&window.raw,
|
||||
&winit::event::WindowEvent::ThemeChanged(theme),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
Action::Widget(operation) => {
|
||||
let mut current_operation = Some(operation);
|
||||
|
|
@ -1521,3 +1645,37 @@ pub fn user_force_quit(
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sysinfo")]
|
||||
fn system_information(
|
||||
graphics: compositor::Information,
|
||||
) -> system::Information {
|
||||
use sysinfo::{Process, System};
|
||||
|
||||
let mut system = System::new_all();
|
||||
system.refresh_all();
|
||||
|
||||
let cpu_brand = system
|
||||
.cpus()
|
||||
.first()
|
||||
.map(|cpu| cpu.brand().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let memory_used = sysinfo::get_current_pid()
|
||||
.and_then(|pid| system.process(pid).ok_or("Process not found"))
|
||||
.map(Process::memory)
|
||||
.ok();
|
||||
|
||||
system::Information {
|
||||
system_name: System::name(),
|
||||
system_kernel: System::kernel_version(),
|
||||
system_version: System::long_os_version(),
|
||||
system_short_version: System::os_version(),
|
||||
cpu_brand,
|
||||
cpu_cores: system.physical_core_count(),
|
||||
memory_total: system.total_memory(),
|
||||
memory_used,
|
||||
graphics_adapter: graphics.adapter,
|
||||
graphics_backend: graphics.backend,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
//! Access the native system.
|
||||
use crate::graphics::compositor;
|
||||
use crate::runtime::system::{Action, Information};
|
||||
use crate::runtime::{self, Task};
|
||||
|
||||
/// Query for available system information.
|
||||
pub fn fetch_information() -> Task<Information> {
|
||||
runtime::task::oneshot(|channel| {
|
||||
runtime::Action::System(Action::QueryInformation(channel))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn information(
|
||||
graphics_info: compositor::Information,
|
||||
) -> Information {
|
||||
use sysinfo::{Process, System};
|
||||
let mut system = System::new_all();
|
||||
system.refresh_all();
|
||||
|
||||
let cpu_brand = system
|
||||
.cpus()
|
||||
.first()
|
||||
.map(|cpu| cpu.brand().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let memory_used = sysinfo::get_current_pid()
|
||||
.and_then(|pid| system.process(pid).ok_or("Process not found"))
|
||||
.map(Process::memory)
|
||||
.ok();
|
||||
|
||||
Information {
|
||||
system_name: System::name(),
|
||||
system_kernel: System::kernel_version(),
|
||||
system_version: System::long_os_version(),
|
||||
system_short_version: System::os_version(),
|
||||
cpu_brand,
|
||||
cpu_cores: system.physical_core_count(),
|
||||
memory_total: system.total_memory(),
|
||||
memory_used,
|
||||
graphics_adapter: graphics_info.adapter,
|
||||
graphics_backend: graphics_info.backend,
|
||||
}
|
||||
}
|
||||
|
|
@ -55,8 +55,9 @@ where
|
|||
program: &program::Instance<P>,
|
||||
compositor: &mut C,
|
||||
exit_on_close_request: bool,
|
||||
system_theme: theme::Mode,
|
||||
) -> &mut Window<P, C> {
|
||||
let state = State::new(program, id, &window);
|
||||
let state = State::new(program, id, &window, system_theme);
|
||||
let viewport_version = state.viewport_version();
|
||||
let physical_size = state.physical_size();
|
||||
let surface = compositor.create_surface(
|
||||
|
|
|
|||
|
|
@ -9,18 +9,20 @@ use winit::window::Window;
|
|||
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
/// The state of a multi-windowed [`Program`].
|
||||
/// The state of the window of a [`Program`].
|
||||
pub struct State<P: Program>
|
||||
where
|
||||
P::Theme: theme::Base,
|
||||
{
|
||||
title: String,
|
||||
scale_factor: f64,
|
||||
scale_factor: f32,
|
||||
viewport: Viewport,
|
||||
viewport_version: u64,
|
||||
cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
|
||||
modifiers: winit::keyboard::ModifiersState,
|
||||
theme: P::Theme,
|
||||
theme: Option<P::Theme>,
|
||||
theme_mode: theme::Mode,
|
||||
default_theme: P::Theme,
|
||||
style: theme::Style,
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +31,7 @@ where
|
|||
P::Theme: theme::Base,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("multi_window::State")
|
||||
f.debug_struct("window::State")
|
||||
.field("title", &self.title)
|
||||
.field("scale_factor", &self.scale_factor)
|
||||
.field("viewport", &self.viewport)
|
||||
|
|
@ -49,18 +51,22 @@ where
|
|||
program: &program::Instance<P>,
|
||||
window_id: window::Id,
|
||||
window: &Window,
|
||||
system_theme: theme::Mode,
|
||||
) -> Self {
|
||||
let title = program.title(window_id);
|
||||
let scale_factor = program.scale_factor(window_id);
|
||||
let theme = program.theme(window_id);
|
||||
let style = program.style(&theme);
|
||||
let theme_mode =
|
||||
theme.as_ref().map(theme::Base::mode).unwrap_or_default();
|
||||
let default_theme = <P::Theme as theme::Base>::default(system_theme);
|
||||
let style = program.style(theme.as_ref().unwrap_or(&default_theme));
|
||||
|
||||
let viewport = {
|
||||
let physical_size = window.inner_size();
|
||||
|
||||
Viewport::with_physical_size(
|
||||
Size::new(physical_size.width, physical_size.height),
|
||||
window.scale_factor() * scale_factor,
|
||||
window.scale_factor() as f32 * scale_factor,
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -72,6 +78,8 @@ where
|
|||
cursor_position: None,
|
||||
modifiers: winit::keyboard::ModifiersState::default(),
|
||||
theme,
|
||||
theme_mode,
|
||||
default_theme,
|
||||
style,
|
||||
}
|
||||
}
|
||||
|
|
@ -99,7 +107,7 @@ where
|
|||
}
|
||||
|
||||
/// Returns the current scale factor of the [`Viewport`] of the [`State`].
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
pub fn scale_factor(&self) -> f32 {
|
||||
self.viewport.scale_factor()
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +131,12 @@ where
|
|||
|
||||
/// Returns the current theme of the [`State`].
|
||||
pub fn theme(&self) -> &P::Theme {
|
||||
&self.theme
|
||||
self.theme.as_ref().unwrap_or(&self.default_theme)
|
||||
}
|
||||
|
||||
/// Returns the current [`theme::Mode`] of the [`State`].
|
||||
pub fn theme_mode(&self) -> theme::Mode {
|
||||
self.theme_mode
|
||||
}
|
||||
|
||||
/// Returns the current background [`Color`] of the [`State`].
|
||||
|
|
@ -137,14 +150,19 @@ where
|
|||
}
|
||||
|
||||
/// Processes the provided window event and updates the [`State`] accordingly.
|
||||
pub fn update(&mut self, window: &Window, event: &WindowEvent) {
|
||||
pub fn update(
|
||||
&mut self,
|
||||
program: &program::Instance<P>,
|
||||
window: &Window,
|
||||
event: &WindowEvent,
|
||||
) {
|
||||
match event {
|
||||
WindowEvent::Resized(new_size) => {
|
||||
let size = Size::new(new_size.width, new_size.height);
|
||||
|
||||
self.viewport = Viewport::with_physical_size(
|
||||
size,
|
||||
window.scale_factor() * self.scale_factor,
|
||||
window.scale_factor() as f32 * self.scale_factor,
|
||||
);
|
||||
|
||||
self.viewport_version = self.viewport_version.wrapping_add(1);
|
||||
|
|
@ -157,7 +175,7 @@ where
|
|||
|
||||
self.viewport = Viewport::with_physical_size(
|
||||
size,
|
||||
new_scale_factor * self.scale_factor,
|
||||
*new_scale_factor as f32 * self.scale_factor,
|
||||
);
|
||||
|
||||
self.viewport_version = self.viewport_version.wrapping_add(1);
|
||||
|
|
@ -174,6 +192,16 @@ where
|
|||
WindowEvent::ModifiersChanged(new_modifiers) => {
|
||||
self.modifiers = new_modifiers.state();
|
||||
}
|
||||
WindowEvent::ThemeChanged(theme) => {
|
||||
self.default_theme = <P::Theme as theme::Base>::default(
|
||||
conversion::theme_mode(*theme),
|
||||
);
|
||||
|
||||
if self.theme.is_none() {
|
||||
self.style = program.style(&self.default_theme);
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -182,7 +210,7 @@ where
|
|||
/// window.
|
||||
///
|
||||
/// Normally, a [`Program`] should be synchronized with its [`State`]
|
||||
/// and window after calling [`State::update`].
|
||||
/// and window after calling [`Program::update`].
|
||||
pub fn synchronize(
|
||||
&mut self,
|
||||
program: &program::Instance<P>,
|
||||
|
|
@ -208,7 +236,7 @@ where
|
|||
{
|
||||
self.viewport = Viewport::with_physical_size(
|
||||
Size::new(new_size.width, new_size.height),
|
||||
window.scale_factor() * new_scale_factor,
|
||||
window.scale_factor() as f32 * new_scale_factor,
|
||||
);
|
||||
self.viewport_version = self.viewport_version.wrapping_add(1);
|
||||
|
||||
|
|
@ -217,6 +245,45 @@ where
|
|||
|
||||
// Update theme and appearance
|
||||
self.theme = program.theme(window_id);
|
||||
self.style = program.style(&self.theme);
|
||||
self.style = program.style(self.theme());
|
||||
|
||||
let new_mode = self
|
||||
.theme
|
||||
.as_ref()
|
||||
.map(theme::Base::mode)
|
||||
.unwrap_or_default();
|
||||
|
||||
if self.theme_mode != new_mode {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
window.set_theme(conversion::window_theme(new_mode));
|
||||
|
||||
// Assume the old mode matches the system one
|
||||
// We will be notified otherwise
|
||||
if new_mode == theme::Mode::None {
|
||||
self.default_theme =
|
||||
<P::Theme as theme::Base>::default(self.theme_mode);
|
||||
|
||||
if self.theme.is_none() {
|
||||
self.style = program.style(&self.default_theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// mundy always notifies system theme changes, so we
|
||||
// just restore the default theme mode.
|
||||
let new_mode = if new_mode == theme::Mode::None {
|
||||
theme::Base::mode(&self.default_theme)
|
||||
} else {
|
||||
new_mode
|
||||
};
|
||||
|
||||
window.set_theme(conversion::window_theme(new_mode));
|
||||
}
|
||||
|
||||
self.theme_mode = new_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue