From 74ee219f83a7bb8369e5dcc2e262d59c2577ec52 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 27 Jan 2023 15:00:37 +0000 Subject: [PATCH 01/25] EventMgr::handle_winit: pass &mut RootWidget --- crates/kas-core/src/event/manager/mgr_shell.rs | 4 ++-- crates/kas-core/src/shell/window.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/kas-core/src/event/manager/mgr_shell.rs b/crates/kas-core/src/event/manager/mgr_shell.rs index 826e0e00b..b156189a8 100644 --- a/crates/kas-core/src/event/manager/mgr_shell.rs +++ b/crates/kas-core/src/event/manager/mgr_shell.rs @@ -14,7 +14,7 @@ use crate::cast::traits::*; use crate::geom::{Coord, DVec2}; use crate::model::SharedRc; use crate::shell::ShellWindow; -use crate::{Action, Widget, WidgetId}; +use crate::{Action, RootWidget, Widget, WidgetId}; // TODO: this should be configurable or derived from the system const DOUBLE_CLICK_TIMEOUT: Duration = Duration::from_secs(1); @@ -324,7 +324,7 @@ impl<'a> EventMgr<'a> { #[cfg_attr(doc_cfg, doc(cfg(feature = "winit")))] pub(crate) fn handle_winit( &mut self, - widget: &mut dyn Widget, + widget: &mut RootWidget, event: winit::event::WindowEvent, ) { use winit::event::{ElementState, MouseScrollDelta, TouchPhase, WindowEvent::*}; diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 00310d3f6..feec8e990 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -173,9 +173,8 @@ impl> Window { } event => { let mut tkw = TkWindow::new(shared, Some(&self.window), &mut self.theme_window); - let widget = self.widget.as_widget_mut(); self.ev_state.with(&mut tkw, |mgr| { - mgr.handle_winit(widget, event); + mgr.handle_winit(&mut self.widget, event); }); if self.ev_state.action.contains(Action::RECONFIGURE) { From 22e4dc4133f71ef9fe3229ce95b8a8639e06e6a0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 27 Jan 2023 15:07:28 +0000 Subject: [PATCH 02/25] Add Window::drag_anywhere --- crates/kas-core/src/core/window.rs | 12 ++++++++++++ crates/kas-core/src/event/manager.rs | 13 ++++++++++--- crates/kas-core/src/event/manager/mgr_shell.rs | 6 +++++- crates/kas-core/src/shell/common.rs | 5 +++++ crates/kas-core/src/shell/window.rs | 6 ++++++ examples/mandlebrot/mandlebrot.rs | 4 ++++ 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/crates/kas-core/src/core/window.rs b/crates/kas-core/src/core/window.rs index f5a484410..a0bde0acb 100644 --- a/crates/kas-core/src/core/window.rs +++ b/crates/kas-core/src/core/window.rs @@ -55,6 +55,18 @@ pub trait Window: Widget { (true, false) } + /// Whether to allow dragging the window from the background + /// + /// If true, then any unhandled click+drag in the window may be used to + /// drag the window. Probably more useful for small pop-ups than large + /// windows. + /// + /// Default: `true`. + #[inline] + fn drag_anywhere(&self) -> bool { + true + } + /// Handle closure of self /// /// This allows for actions on destruction. diff --git a/crates/kas-core/src/event/manager.rs b/crates/kas-core/src/event/manager.rs index 57c46f603..9c653aebf 100644 --- a/crates/kas-core/src/event/manager.rs +++ b/crates/kas-core/src/event/manager.rs @@ -701,7 +701,13 @@ impl<'a> EventMgr<'a> { self.send_recurse(widget, id, disabled, event) == Response::Used } - fn send_popup_first(&mut self, widget: &mut dyn Widget, id: Option, event: Event) { + // Returns true if event is used + fn send_popup_first( + &mut self, + widget: &mut dyn Widget, + id: Option, + event: Event, + ) -> bool { while let Some((wid, parent)) = self .popups .last() @@ -709,12 +715,13 @@ impl<'a> EventMgr<'a> { { log::trace!("send_popup_first: parent={parent}: {event:?}"); if self.send_event(widget, parent, event.clone()) { - return; + return true; } self.close_window(wid, false); } if let Some(id) = id { - self.send_event(widget, id, event); + return self.send_event(widget, id, event); } + false } } diff --git a/crates/kas-core/src/event/manager/mgr_shell.rs b/crates/kas-core/src/event/manager/mgr_shell.rs index b156189a8..686159e58 100644 --- a/crates/kas-core/src/event/manager/mgr_shell.rs +++ b/crates/kas-core/src/event/manager/mgr_shell.rs @@ -494,7 +494,11 @@ impl<'a> EventMgr<'a> { start_id: self.hover.clone(), coord, }; - self.send_popup_first(widget, self.hover.clone(), event); + let used = self.send_popup_first(widget, self.hover.clone(), event); + + if !used && widget.drag_anywhere() { + self.shell.drag_window(); + } } } // TouchpadPressure { pressure: f32, stage: i64, }, diff --git a/crates/kas-core/src/shell/common.rs b/crates/kas-core/src/shell/common.rs index 6ee0f578f..b0f32d71e 100644 --- a/crates/kas-core/src/shell/common.rs +++ b/crates/kas-core/src/shell/common.rs @@ -131,6 +131,11 @@ pub(crate) trait ShellWindow { /// windows, will receive an update. fn update_all(&mut self, id: UpdateId, payload: u64); + /// Enable window dragging for current click + /// + /// This calls `winit::window::Window::drag_window`. Errors are ignored. + fn drag_window(&self); + /// Attempt to get clipboard contents /// /// In case of failure, paste actions will simply fail. The implementation diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index feec8e990..e2d7ab1cb 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -480,6 +480,12 @@ where self.shared.update_all(id, payload); } + fn drag_window(&self) { + if let Some(window) = self.window { + let _result = window.drag_window(); + } + } + #[inline] fn get_clipboard(&mut self) -> Option { self.shared.get_clipboard() diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index 9065b72c0..5f3ca22fc 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -480,6 +480,10 @@ impl_scope! { fn title(&self) -> &str { "Mandlebrot" } + + fn drag_anywhere(&self) -> bool { + false + } } } From 310c883e6ad71f9374e2855bb7ca3fe333ff8906 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 27 Jan 2023 15:19:18 +0000 Subject: [PATCH 03/25] Fix: remove Deref impl on RootWidget --- .../kas-core/src/event/manager/mgr_shell.rs | 2 +- crates/kas-core/src/root.rs | 32 +++++++++++++++++-- crates/kas-core/src/shell/window.rs | 2 +- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/crates/kas-core/src/event/manager/mgr_shell.rs b/crates/kas-core/src/event/manager/mgr_shell.rs index 686159e58..2d287090f 100644 --- a/crates/kas-core/src/event/manager/mgr_shell.rs +++ b/crates/kas-core/src/event/manager/mgr_shell.rs @@ -14,7 +14,7 @@ use crate::cast::traits::*; use crate::geom::{Coord, DVec2}; use crate::model::SharedRc; use crate::shell::ShellWindow; -use crate::{Action, RootWidget, Widget, WidgetId}; +use crate::{Action, Layout, RootWidget, Widget, WidgetId, Window}; // TODO: this should be configurable or derived from the system const DOUBLE_CLICK_TIMEOUT: Duration = Duration::from_secs(1); diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index 42537bd9c..21102b9e8 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -11,14 +11,13 @@ use crate::geom::{Coord, Offset, Rect, Size}; use crate::layout::{self, AxisInfo, SizeRules}; use crate::theme::{DrawMgr, SizeMgr}; use crate::{Action, Layout, Widget, WidgetExt, WidgetId, Window, WindowId}; -use kas_macros::{autoimpl, impl_scope}; +use kas_macros::impl_scope; use smallvec::SmallVec; impl_scope! { /// A support layer around a window #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] - #[autoimpl(Deref, DerefMut using self.w)] #[derive(Debug)] #[widget] pub struct RootWidget { @@ -75,6 +74,35 @@ impl_scope! { mgr.config_mgr(|mgr| self.resize_popups(mgr)); } } + + // Note: we do not simply Deref to self.w; that allows skipping our Layout + // and Widget methods. + impl Window for Self { + #[inline] + fn title(&self) -> &str { + self.w.title() + } + + #[inline] + fn icon(&self) -> Option { + self.w.icon() + } + + #[inline] + fn restrict_dimensions(&self) -> (bool, bool) { + self.w.restrict_dimensions() + } + + #[inline] + fn drag_anywhere(&self) -> bool { + self.w.drag_anywhere() + } + + #[inline] + fn handle_closure(&mut self, mgr: &mut EventMgr) { + self.w.handle_closure(mgr); + } + } } impl RootWidget { diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index e2d7ab1cb..2891fa6db 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -13,7 +13,7 @@ use kas::geom::{Coord, Rect, Size}; use kas::layout::SolveCache; use kas::theme::{DrawMgr, SizeMgr, ThemeControl, ThemeSize}; use kas::theme::{Theme, Window as _}; -use kas::{Action, Layout, WidgetCore, WidgetExt, WindowId}; +use kas::{Action, Layout, WidgetCore, WidgetExt, Window as _, WindowId}; use std::mem::take; use std::time::Instant; use winit::error::OsError; From 64a1b2f1887bfa042d2faebc8ccbc51e341d183f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 27 Jan 2023 16:36:16 +0000 Subject: [PATCH 04/25] Revise ThemeControl API --- crates/kas-core/src/theme/colors.rs | 20 ++++++++--- crates/kas-core/src/theme/flat_theme.rs | 16 +++++++-- crates/kas-core/src/theme/multi.rs | 22 +++++++++++- crates/kas-core/src/theme/simple_theme.rs | 41 ++++++++++++----------- crates/kas-core/src/theme/traits.rs | 36 ++++++++++++++++---- crates/kas-wgpu/src/shaded_theme.rs | 16 +++++++-- 6 files changed, 117 insertions(+), 34 deletions(-) diff --git a/crates/kas-core/src/theme/colors.rs b/crates/kas-core/src/theme/colors.rs index a4da4fca4..edf32a70c 100644 --- a/crates/kas-core/src/theme/colors.rs +++ b/crates/kas-core/src/theme/colors.rs @@ -171,8 +171,8 @@ pub type ColorsSrgb = Colors; /// [`Colors`] parameterised for graphics usage pub type ColorsLinear = Colors; -impl From for ColorsLinear { - fn from(col: ColorsSrgb) -> Self { +impl From<&ColorsSrgb> for ColorsLinear { + fn from(col: &ColorsSrgb) -> Self { Colors { is_dark: col.is_dark, background: col.background.into(), @@ -191,8 +191,14 @@ impl From for ColorsLinear { } } -impl From for ColorsSrgb { - fn from(col: ColorsLinear) -> Self { +impl From for ColorsLinear { + fn from(col: ColorsSrgb) -> Self { + Colors::from(&col) + } +} + +impl From<&ColorsLinear> for ColorsSrgb { + fn from(col: &ColorsLinear) -> Self { Colors { is_dark: col.is_dark, background: col.background.into(), @@ -211,6 +217,12 @@ impl From for ColorsSrgb { } } +impl From for ColorsSrgb { + fn from(col: ColorsLinear) -> Self { + Colors::from(&col) + } +} + impl Default for ColorsLinear { #[cfg(feature = "dark-light")] fn default() -> Self { diff --git a/crates/kas-core/src/theme/flat_theme.rs b/crates/kas-core/src/theme/flat_theme.rs index 2c133df06..2f04e91f2 100644 --- a/crates/kas-core/src/theme/flat_theme.rs +++ b/crates/kas-core/src/theme/flat_theme.rs @@ -159,12 +159,24 @@ impl ThemeControl for FlatTheme { self.base.set_font_size(pt_size) } + fn active_scheme(&self) -> &str { + self.base.active_scheme() + } + fn list_schemes(&self) -> Vec<&str> { self.base.list_schemes() } - fn set_scheme(&mut self, name: &str) -> Action { - self.base.set_scheme(name) + fn get_scheme(&self, name: &str) -> Option<&super::ColorsSrgb> { + self.base.get_scheme(name) + } + + fn get_colors(&self) -> &ColorsLinear { + self.base.get_colors() + } + + fn set_colors(&mut self, name: String, cols: ColorsLinear) -> Action { + self.base.set_colors(name, cols) } } diff --git a/crates/kas-core/src/theme/multi.rs b/crates/kas-core/src/theme/multi.rs index e668b1dc2..c0e094c26 100644 --- a/crates/kas-core/src/theme/multi.rs +++ b/crates/kas-core/src/theme/multi.rs @@ -150,12 +150,16 @@ impl ThemeControl for MultiTheme { action } + fn active_scheme(&self) -> &str { + self.themes[self.active].active_scheme() + } + fn set_scheme(&mut self, scheme: &str) -> Action { // Slightly inefficient, but sufficient: update all // (Otherwise we would have to call set_scheme in set_theme too.) let mut action = Action::empty(); for theme in &mut self.themes { - action = action.max(theme.set_scheme(scheme)); + action |= theme.set_scheme(scheme); } action } @@ -166,6 +170,22 @@ impl ThemeControl for MultiTheme { self.themes[self.active].list_schemes() } + fn get_scheme(&self, name: &str) -> Option<&super::ColorsSrgb> { + self.themes[self.active].get_scheme(name) + } + + fn get_colors(&self) -> &ColorsLinear { + self.themes[self.active].get_colors() + } + + fn set_colors(&mut self, name: String, cols: ColorsLinear) -> Action { + let mut action = Action::empty(); + for theme in &mut self.themes { + action |= theme.set_colors(name.clone(), cols.clone()); + } + action + } + fn set_theme(&mut self, theme: &str) -> Action { if let Some(index) = self.names.get(theme).cloned() { if index != self.active { diff --git a/crates/kas-core/src/theme/simple_theme.rs b/crates/kas-core/src/theme/simple_theme.rs index 49c19b9b9..1e240590f 100644 --- a/crates/kas-core/src/theme/simple_theme.rs +++ b/crates/kas-core/src/theme/simple_theme.rs @@ -70,17 +70,9 @@ impl SimpleTheme { #[inline] #[must_use] pub fn with_colours(mut self, name: &str) -> Self { - if let Some(scheme) = self.config.get_color_scheme(name) { - self.config.set_active_scheme(name); - let _ = self.set_colors(scheme.into()); - } + let _ = self.set_scheme(name); self } - - pub fn set_colors(&mut self, cols: ColorsLinear) -> Action { - self.cols = cols; - Action::REDRAW - } } pub struct DrawHandle<'a, DS: DrawSharedImpl> { @@ -105,8 +97,9 @@ where fn apply_config(&mut self, config: &Self::Config) -> Action { let mut action = self.config.apply_config(config); - if let Some(scheme) = self.config.get_active_scheme() { - action |= self.set_colors(scheme.into()); + if let Some(cols) = self.config.get_active_scheme() { + self.cols = cols.into(); + action |= Action::REDRAW; } action } @@ -169,6 +162,10 @@ impl ThemeControl for SimpleTheme { Action::RESIZE | Action::THEME_UPDATE } + fn active_scheme(&self) -> &str { + self.config.active_scheme() + } + fn list_schemes(&self) -> Vec<&str> { self.config .color_schemes_iter() @@ -176,14 +173,20 @@ impl ThemeControl for SimpleTheme { .collect() } - fn set_scheme(&mut self, name: &str) -> Action { - if name != self.config.active_scheme() { - if let Some(scheme) = self.config.get_color_scheme(name) { - self.config.set_active_scheme(name); - return self.set_colors(scheme.into()); - } - } - Action::empty() + fn get_scheme(&self, name: &str) -> Option<&super::ColorsSrgb> { + self.config + .color_schemes_iter() + .find_map(|item| (name == item.0).then_some(item.1)) + } + + fn get_colors(&self) -> &ColorsLinear { + &self.cols + } + + fn set_colors(&mut self, name: String, cols: ColorsLinear) -> Action { + self.config.set_active_scheme(name); + self.cols = cols; + Action::REDRAW } } diff --git a/crates/kas-core/src/theme/traits.rs b/crates/kas-core/src/theme/traits.rs index ac459df3b..0380496d5 100644 --- a/crates/kas-core/src/theme/traits.rs +++ b/crates/kas-core/src/theme/traits.rs @@ -5,7 +5,7 @@ //! Theme traits -use super::{ColorsLinear, RasterConfig, ThemeDraw, ThemeSize}; +use super::{ColorsLinear, ColorsSrgb, RasterConfig, ThemeDraw, ThemeSize}; use crate::draw::{color, DrawIface, DrawSharedImpl, SharedState}; use crate::event::EventState; use crate::{autoimpl, Action}; @@ -25,14 +25,38 @@ pub trait ThemeControl { /// Units: Points per Em (standard unit of font size) fn set_font_size(&mut self, pt_size: f32) -> Action; - /// Change the colour scheme - /// - /// If no scheme by this name is found the scheme is left unchanged. - fn set_scheme(&mut self, scheme: &str) -> Action; + /// Get the name of the active color scheme + fn active_scheme(&self) -> &str; - /// List available colour schemes + /// List available color schemes fn list_schemes(&self) -> Vec<&str>; + /// Get colors of a named scheme + fn get_scheme(&self, name: &str) -> Option<&ColorsSrgb>; + + /// Access the in-use color scheme + fn get_colors(&self) -> &ColorsLinear; + + /// Set colors directly + /// + /// This may be used to provide a custom color scheme. The `name` is + /// compulsary (and returned by [`Self::get_active_scheme`]). + /// The `name` is also used when saving config, though the custom colors are + /// not currently saved in this config. + fn set_colors(&mut self, name: String, scheme: ColorsLinear) -> Action; + + /// Change the color scheme + /// + /// If no scheme by this name is found the scheme is left unchanged. + fn set_scheme(&mut self, name: &str) -> Action { + if name != self.active_scheme() { + if let Some(scheme) = self.get_scheme(name) { + return self.set_colors(name.to_string(), scheme.into()); + } + } + Action::empty() + } + /// Switch the theme /// /// Most themes do not react to this method; [`super::MultiTheme`] uses diff --git a/crates/kas-wgpu/src/shaded_theme.rs b/crates/kas-wgpu/src/shaded_theme.rs index a8546b49f..fd0519c13 100644 --- a/crates/kas-wgpu/src/shaded_theme.rs +++ b/crates/kas-wgpu/src/shaded_theme.rs @@ -153,12 +153,24 @@ impl ThemeControl for ShadedTheme { self.base.set_font_size(pt_size) } + fn active_scheme(&self) -> &str { + self.base.active_scheme() + } + fn list_schemes(&self) -> Vec<&str> { self.base.list_schemes() } - fn set_scheme(&mut self, name: &str) -> Action { - self.base.set_scheme(name) + fn get_scheme(&self, name: &str) -> Option<&kas::theme::ColorsSrgb> { + self.base.get_scheme(name) + } + + fn get_colors(&self) -> &ColorsLinear { + self.base.get_colors() + } + + fn set_colors(&mut self, name: String, cols: ColorsLinear) -> Action { + self.base.set_colors(name, cols) } } From 01d08bf0cb2e8d0570257c678e29fed46ec57b2e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 27 Jan 2023 16:42:28 +0000 Subject: [PATCH 05/25] Add Window::transparent; make examples/clock transparent --- crates/kas-core/src/core/window.rs | 16 +++++++++++++--- crates/kas-core/src/root.rs | 5 +++++ crates/kas-core/src/shell/window.rs | 1 + crates/kas-wgpu/src/surface.rs | 4 ++++ examples/clock.rs | 12 +++++++++++- 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/crates/kas-core/src/core/window.rs b/crates/kas-core/src/core/window.rs index a0bde0acb..4c2439542 100644 --- a/crates/kas-core/src/core/window.rs +++ b/crates/kas-core/src/core/window.rs @@ -33,7 +33,6 @@ pub trait Window: Widget { /// Get the window icon, if any /// /// Default: `None` - #[inline] fn icon(&self) -> Option { None } @@ -50,7 +49,6 @@ pub trait Window: Widget { /// windows. /// /// Default: `(true, false)` - #[inline] fn restrict_dimensions(&self) -> (bool, bool) { (true, false) } @@ -62,11 +60,23 @@ pub trait Window: Widget { /// windows. /// /// Default: `true`. - #[inline] fn drag_anywhere(&self) -> bool { true } + /// Whether the window supports transparency + /// + /// If true, painting with `alpha < 1.0` makes the background visible. + /// + /// Note: results may vary by platform. Current output does *not* use + /// pre-multiplied alpha which *some* platforms expect, thus pixels with + /// partial transparency may have incorrect appearance. + /// + /// Default: `false`. + fn transparent(&self) -> bool { + false + } + /// Handle closure of self /// /// This allows for actions on destruction. diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index 21102b9e8..32c8c827b 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -98,6 +98,11 @@ impl_scope! { self.w.drag_anywhere() } + #[inline] + fn transparent(&self) -> bool { + self.w.transparent() + } + #[inline] fn handle_closure(&mut self, mgr: &mut EventMgr) { self.w.handle_closure(mgr); diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 2891fa6db..061e2d42a 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -102,6 +102,7 @@ impl> Window { let window = builder .with_title(widget.title()) .with_window_icon(widget.icon()) + .with_transparent(widget.transparent()) .build(elwt)?; shared.init_clipboard(&window); diff --git a/crates/kas-wgpu/src/surface.rs b/crates/kas-wgpu/src/surface.rs index 08d068f57..7e17a75cf 100644 --- a/crates/kas-wgpu/src/surface.rs +++ b/crates/kas-wgpu/src/surface.rs @@ -38,6 +38,10 @@ impl WindowSurface for Surface { width: size.0.cast(), height: size.1.cast(), present_mode: wgpu::PresentMode::Fifo, + // FIXME: current output is for Opaque or PostMultiplied, depending + // on window transparency. But we can't pick what we want since only + // a sub-set of modes are supported (depending on target). + // Currently it's unclear how to handle this properly. alpha_mode: wgpu::CompositeAlphaMode::Auto, }; surface.configure(&shared.device, &sc_desc); diff --git a/examples/clock.rs b/examples/clock.rs index b0784afb2..acd6eedea 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -19,6 +19,7 @@ use kas::draw::{color, Draw, DrawRounded, PassType}; use kas::geom::{Offset, Quad, Rect, Vec2}; use kas::prelude::*; use kas::shell::ShellAssoc; +use kas::theme::ThemeControl; type Theme = kas::theme::FlatTheme; type Shell = kas::shell::DefaultShell; @@ -166,11 +167,20 @@ impl_scope! { fn title(&self) -> &str { "Clock" } + + fn transparent(&self) -> bool { + true + } } } fn main() -> kas::shell::Result<()> { env_logger::init(); - Shell::new(Theme::new())?.with(Clock::new())?.run() + let mut theme = Theme::new(); + let mut cols = theme.get_colors().clone(); + cols.background.a = 0.5; + let _ = theme.set_colors("transparent".to_string(), cols); + + Shell::new(theme)?.with(Clock::new())?.run() } From 183acac8fac2a66a5c3dea2dee9cde6588c8f5cd Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 27 Jan 2023 17:04:12 +0000 Subject: [PATCH 06/25] Tweak examples/clock.rs for appearance --- examples/clock.rs | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/examples/clock.rs b/examples/clock.rs index acd6eedea..3bd424f61 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -13,9 +13,11 @@ extern crate chrono; use chrono::prelude::*; use std::f32::consts::PI; +use std::str::FromStr; use std::time::Duration; -use kas::draw::{color, Draw, DrawRounded, PassType}; +use kas::draw::color::{Rgba, Rgba8Srgb}; +use kas::draw::{Draw, DrawRounded}; use kas::geom::{Offset, Quad, Rect, Vec2}; use kas::prelude::*; use kas::shell::ShellAssoc; @@ -38,7 +40,7 @@ impl_scope! { impl Layout for Clock { fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules { - kas::layout::LogicalSize(100.0, 100.0) + kas::layout::LogicalSize(64.0, 64.0) .to_rules_with_factor(axis, mgr.scale_factor(), 3.0) .with_stretch(Stretch::High) } @@ -52,27 +54,30 @@ impl_scope! { let pos = rect.pos + excess / 2; self.core.rect = Rect { pos, size }; - let text_height = size.1 / 3; - let text_size = Size(size.0, text_height); + let text_size = Size(size.0, size.1 / 4); + let text_height = text_size.1 as f32; let mut env = self.date.env(); - env.dpem = text_height as f32 * 0.4; + env.dpem = text_height * 0.5; env.bounds = text_size.cast(); self.date.update_env(env).expect("invalid font_id"); - env.dpem = text_height as f32 * 0.5; + env.dpem = text_height * 0.7; self.time.update_env(env).expect("invalid font_id"); - let y_mid = pos.0 + size.1 / 2; - self.date_rect = Rect::new(Coord(pos.0, y_mid - text_height), text_size); - self.time_rect = Rect::new(Coord(pos.0, y_mid), text_size); + let time_pos = pos + Offset(0, size.1 * 5 / 8); + let date_pos = pos + Offset(0, size.1 / 8); + self.date_rect = Rect::new(date_pos, text_size); + self.time_rect = Rect::new(time_pos, text_size); } fn draw(&mut self, mut draw: DrawMgr) { - let col_face = color::Rgba::grey(0.4); - let col_time = color::Rgba::grey(0.0); - let col_date = color::Rgba::grey(0.2); - let col_hands = color::Rgba8Srgb::rgb(124, 124, 170).into(); - let col_secs = color::Rgba8Srgb::rgb(203, 124, 124).into(); + let accent: Rgba = Rgba8Srgb::from_str("#d7916f").unwrap().into(); + let col_back = Rgba::ga(0.0, 0.5); + let col_face = accent.multiply(0.4); + let col_time = Rgba::grey(1.0); + let col_date = Rgba::grey(0.8); + let col_hands = accent.multiply(0.7); + let col_secs = accent; // We use the low-level draw device to draw our clock. This means it is // not themeable, but gives us much more flexible draw routines. @@ -80,7 +85,8 @@ impl_scope! { let rect = self.core.rect; let quad = Quad::conv(rect); - draw.circle(quad, 0.95, col_face); + draw.circle(quad, 0.0, col_back); + draw.circle(quad, 0.98, col_face); let half = (quad.b.1 - quad.a.1) / 2.0; let centre = quad.a + half; @@ -98,8 +104,6 @@ impl_scope! { draw.text(self.date_rect, self.date.as_ref(), col_date); draw.text(self.time_rect, self.time.as_ref(), col_time); - // We use a new pass to control the draw order (force in front). - let mut draw = draw.new_pass(rect, Offset::ZERO, PassType::Clip); let mut line_seg = |t: f32, r1: f32, r2: f32, w, col| { let v = Vec2(t.sin(), -t.cos()); draw.rounded_line(centre + v * r1, centre + v * r2, w, col); @@ -179,7 +183,7 @@ fn main() -> kas::shell::Result<()> { let mut theme = Theme::new(); let mut cols = theme.get_colors().clone(); - cols.background.a = 0.5; + cols.background = Rgba::ga(0.0, 0.0); let _ = theme.set_colors("transparent".to_string(), cols); Shell::new(theme)?.with(Clock::new())?.run() From 6cf3dc140c7be73185d8c1d2754ed78eec62e876 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 27 Jan 2023 17:25:58 +0000 Subject: [PATCH 07/25] Add Window::decorations; support Decorations::None --- crates/kas-core/src/core/window.rs | 26 ++++++++++++++++++++++++++ crates/kas-core/src/root.rs | 5 +++++ crates/kas-core/src/shell/window.rs | 1 + examples/clock.rs | 4 ++++ 4 files changed, 36 insertions(+) diff --git a/crates/kas-core/src/core/window.rs b/crates/kas-core/src/core/window.rs index 4c2439542..c127c9053 100644 --- a/crates/kas-core/src/core/window.rs +++ b/crates/kas-core/src/core/window.rs @@ -25,6 +25,17 @@ impl WindowId { } } +/// Available decoration modes +#[derive(PartialEq, Eq, Hash, Debug)] +pub enum Decorations { + /// No decorations + None, + /// Server-side decorations + /// + /// Decorations are drawn by the window manager, if available. + Server, +} + /// Functionality required by a window pub trait Window: Widget { /// Get the window title @@ -37,6 +48,21 @@ pub trait Window: Widget { None } + /// Get the preference for window decorations + /// + /// "Windowing" platforms (i.e. not mobile or web) usually include a + /// title-bar, icons and potentially side borders. These are known as + /// **decorations**. + /// + /// This controls the *preferred* type of decorations on windowing + /// platforms. It is not always followed (e.g. Wayland does not support + /// server-side decorations by default). + /// + /// Default: [`Decorations::Server`]. + fn decorations(&self) -> Decorations { + Decorations::Server + } + /// Whether to limit the maximum size of a window /// /// All widgets' size rules allow calculation of two sizes: the minimum diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index 32c8c827b..83e156009 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -88,6 +88,11 @@ impl_scope! { self.w.icon() } + #[inline] + fn decorations(&self) -> crate::Decorations { + self.w.decorations() + } + #[inline] fn restrict_dimensions(&self) -> (bool, bool) { self.w.restrict_dimensions() diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 061e2d42a..757ec9c33 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -102,6 +102,7 @@ impl> Window { let window = builder .with_title(widget.title()) .with_window_icon(widget.icon()) + .with_decorations(widget.decorations() == kas::Decorations::Server) .with_transparent(widget.transparent()) .build(elwt)?; diff --git a/examples/clock.rs b/examples/clock.rs index 3bd424f61..4a322f1f3 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -172,6 +172,10 @@ impl_scope! { "Clock" } + fn decorations(&self) -> kas::Decorations { + kas::Decorations::None + } + fn transparent(&self) -> bool { true } From bfda4558ee9661eab68d43477a7617810fe24005 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 31 Jan 2023 16:15:45 +0000 Subject: [PATCH 08/25] Doc: CustomPipe::render_final --- crates/kas-wgpu/src/draw/custom.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/kas-wgpu/src/draw/custom.rs b/crates/kas-wgpu/src/draw/custom.rs index 19e3de1cb..3a103e09a 100644 --- a/crates/kas-wgpu/src/draw/custom.rs +++ b/crates/kas-wgpu/src/draw/custom.rs @@ -116,11 +116,8 @@ pub trait CustomPipe: 'static { /// Render (final) /// - /// This method is the last step in drawing a frame except for text - /// rendering. Depending on the application, it may make more sense to draw - /// in [`CustomPipe::render_pass`] or in this method. - /// - /// This method is optional; by default it does nothing. + /// This method is the last step in drawing a frame. Usually (including + /// in the default implementation) it does nothing. #[allow(unused)] fn render_final<'a>( &'a self, From 56ea8c43d14779ec2e202d8a8b908c2629219957 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 5 Feb 2023 13:56:51 +0000 Subject: [PATCH 09/25] Add Decorations::Border, Theme --- crates/kas-core/src/core/window.rs | 15 +++++++++++++ crates/kas-core/src/root.rs | 26 +++++++++++++++++++---- crates/kas-core/src/theme/dimensions.rs | 1 + crates/kas-core/src/theme/flat_theme.rs | 2 +- crates/kas-core/src/theme/simple_theme.rs | 2 +- crates/kas-core/src/theme/style.rs | 2 ++ crates/kas-wgpu/src/shaded_theme.rs | 8 +++++++ examples/stopwatch.rs | 8 ++++++- 8 files changed, 57 insertions(+), 7 deletions(-) diff --git a/crates/kas-core/src/core/window.rs b/crates/kas-core/src/core/window.rs index c127c9053..a44c3cb45 100644 --- a/crates/kas-core/src/core/window.rs +++ b/crates/kas-core/src/core/window.rs @@ -26,10 +26,25 @@ impl WindowId { } /// Available decoration modes +/// +/// See [`Window::decorations`]. #[derive(PartialEq, Eq, Hash, Debug)] pub enum Decorations { /// No decorations + /// + /// The root widget is drawn as a simple rectangle with no borders. None, + /// Add a simple themed border to the widget + /// + /// Probably looks better if [`Window::transparent`] is true. + Border, + /// Toolkit-drawn decorations + /// + /// Decorations will match the toolkit theme, not the platform theme. + /// These decorations may not have all the same capabilities. + /// + /// Probably looks better if [`Window::transparent`] is true. + Toolkit, /// Server-side decorations /// /// Decorations are drawn by the window manager, if available. diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index 83e156009..4ff8cb951 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -9,8 +9,8 @@ use crate::dir::Directional; use crate::event::{ConfigMgr, EventMgr, Scroll}; use crate::geom::{Coord, Offset, Rect, Size}; use crate::layout::{self, AxisInfo, SizeRules}; -use crate::theme::{DrawMgr, SizeMgr}; -use crate::{Action, Layout, Widget, WidgetExt, WidgetId, Window, WindowId}; +use crate::theme::{DrawMgr, FrameStyle, SizeMgr}; +use crate::{Action, Decorations, Layout, Widget, WidgetExt, WidgetId, Window, WindowId}; use kas_macros::impl_scope; use smallvec::SmallVec; @@ -24,18 +24,31 @@ impl_scope! { core: widget_core!(), #[widget] w: Box, + dec_offset: Offset, + dec_size: Size, popups: SmallVec<[(WindowId, kas::Popup, Offset); 16]>, } impl Layout for RootWidget { #[inline] fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules { - self.w.size_rules(size_mgr, axis) + let inner = self.w.size_rules(size_mgr.re(), axis); + if matches!(self.w.decorations(), Decorations::Border | Decorations::Toolkit) { + let frame = size_mgr.frame(FrameStyle::Window, axis); + let (rules, offset, size) = frame.surround(inner); + self.dec_offset.set_component(axis, offset); + self.dec_size.set_component(axis, size); + rules + } else { + inner + } } #[inline] - fn set_rect(&mut self, mgr: &mut ConfigMgr, rect: Rect) { + fn set_rect(&mut self, mgr: &mut ConfigMgr, mut rect: Rect) { self.core.rect = rect; + rect.pos += self.dec_offset; + rect.size -= self.dec_size; self.w.set_rect(mgr, rect); } @@ -56,6 +69,9 @@ impl_scope! { } fn draw(&mut self, mut draw: DrawMgr) { + if self.dec_size != Size::ZERO { + draw.frame(self.core.rect, FrameStyle::Window, Default::default()); + } draw.recurse(&mut self.w); for (_, popup, translation) in &self.popups { if let Some(widget) = self.w.find_widget_mut(&popup.id) { @@ -121,6 +137,8 @@ impl RootWidget { RootWidget { core: Default::default(), w, + dec_offset: Default::default(), + dec_size: Default::default(), popups: Default::default(), } } diff --git a/crates/kas-core/src/theme/dimensions.rs b/crates/kas-core/src/theme/dimensions.rs index d8e119fc3..4bb8fb91b 100644 --- a/crates/kas-core/src/theme/dimensions.rs +++ b/crates/kas-core/src/theme/dimensions.rs @@ -290,6 +290,7 @@ impl ThemeSize for Window { let outer = self.dims.m_large; match style { FrameStyle::Frame => FrameRules::new_sym(self.dims.frame, 0, outer), + FrameStyle::Window => FrameRules::new_sym(self.dims.frame, 0, 0), FrameStyle::Popup => FrameRules::new_sym(self.dims.popup_frame, 0, 0), FrameStyle::MenuEntry => FrameRules::new_sym(self.dims.menu_frame, 0, 0), FrameStyle::NavFocus => FrameRules::new_sym(0, self.dims.m_inner, 0), diff --git a/crates/kas-core/src/theme/flat_theme.rs b/crates/kas-core/src/theme/flat_theme.rs index 2f04e91f2..af3ed61ee 100644 --- a/crates/kas-core/src/theme/flat_theme.rs +++ b/crates/kas-core/src/theme/flat_theme.rs @@ -347,7 +347,7 @@ where fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background) { let outer = Quad::conv(rect); match style { - FrameStyle::Frame => { + FrameStyle::Frame | FrameStyle::Window => { let inner = outer.shrink(self.w.dims.frame as f32); self.draw .rounded_frame(outer, inner, BG_SHRINK_FACTOR, self.cols.frame); diff --git a/crates/kas-core/src/theme/simple_theme.rs b/crates/kas-core/src/theme/simple_theme.rs index 1e240590f..340c4c31b 100644 --- a/crates/kas-core/src/theme/simple_theme.rs +++ b/crates/kas-core/src/theme/simple_theme.rs @@ -272,7 +272,7 @@ where fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background) { let outer = Quad::conv(rect); match style { - FrameStyle::Frame => { + FrameStyle::Frame | FrameStyle::Window => { let inner = outer.shrink(self.w.dims.frame as f32); self.draw.frame(outer, inner, self.cols.frame); } diff --git a/crates/kas-core/src/theme/style.rs b/crates/kas-core/src/theme/style.rs index 6288eb16c..9c420bb61 100644 --- a/crates/kas-core/src/theme/style.rs +++ b/crates/kas-core/src/theme/style.rs @@ -88,6 +88,8 @@ pub enum FrameStyle { Button, /// Box used to contain editable text EditBox, + /// Window decoration (excludes top buttons) + Window, } /// Class of text drawn diff --git a/crates/kas-wgpu/src/shaded_theme.rs b/crates/kas-wgpu/src/shaded_theme.rs index fd0519c13..93a2889fe 100644 --- a/crates/kas-wgpu/src/shaded_theme.rs +++ b/crates/kas-wgpu/src/shaded_theme.rs @@ -307,6 +307,14 @@ where let bg_col = self.cols.from_edit_bg(bg, state); self.draw_edit_box(rect, bg_col, state.nav_focus()); } + FrameStyle::Window => { + let outer = Quad::conv(rect); + let inner = outer.shrink(self.w.dims.frame as f32); + let col = self.cols.background; + self.draw + .shaded_round_frame(outer, inner, NORMS_RAISED, col); + self.draw.rect(inner, col); + } style => self.as_flat().frame(id, rect, style, bg), } } diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index cf07ed509..a9547af60 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -10,7 +10,7 @@ use std::time::{Duration, Instant}; use kas::class::HasString; use kas::event::{ConfigMgr, Event, EventMgr, Response}; use kas::widgets::{Frame, Label, TextButton}; -use kas::{Widget, WidgetCore, WidgetExt, Window}; +use kas::{Decorations, Widget, WidgetCore, WidgetExt, Window}; #[derive(Clone, Debug)] struct MsgReset; @@ -71,6 +71,12 @@ fn make_window() -> Box { } impl Window for Self { fn title(&self) -> &str { "Stopwatch" } + fn decorations(&self) -> Decorations { + Decorations::Border + } + fn transparent(&self) -> bool { + true + } fn restrict_dimensions(&self) -> (bool, bool) { (true, true) } From b3e523ab899a2c3e16dd72b3addfe3a5a6d88dfe Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 5 Feb 2023 18:30:25 +0000 Subject: [PATCH 10/25] Automatically adjust clear_color when window is transparent --- crates/kas-core/src/core/window.rs | 2 ++ crates/kas-core/src/shell/window.rs | 8 ++++++-- crates/kas-core/src/theme/traits.rs | 4 +++- examples/clock.rs | 7 +------ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/kas-core/src/core/window.rs b/crates/kas-core/src/core/window.rs index a44c3cb45..21b5f4559 100644 --- a/crates/kas-core/src/core/window.rs +++ b/crates/kas-core/src/core/window.rs @@ -108,6 +108,8 @@ pub trait Window: Widget { /// Whether the window supports transparency /// /// If true, painting with `alpha < 1.0` makes the background visible. + /// Additionally, window draw targets are cleared to transparent. This does + /// not stop theme elements from drawing a solid background. /// /// Note: results may vary by platform. Current output does *not* use /// pre-multiplied alpha which *some* platforms expect, thus pixels with diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 757ec9c33..8991ccffd 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -7,7 +7,7 @@ use super::{PendingAction, ProxyAction, SharedState, ShellWindow, WindowSurface}; use kas::cast::Cast; -use kas::draw::{AnimationState, DrawShared}; +use kas::draw::{color::Rgba, AnimationState, DrawShared}; use kas::event::{ConfigMgr, CursorIcon, EventState, UpdateId}; use kas::geom::{Coord, Rect, Size}; use kas::layout::SolveCache; @@ -392,7 +392,11 @@ impl> Window { return Err(()); } - let clear_color = shared.theme.clear_color(); + let clear_color = if self.widget.transparent() { + Rgba::TRANSPARENT + } else { + shared.theme.clear_color() + }; self.surface.present(&mut shared.draw.draw, clear_color); let text_dur_micros = take(&mut self.surface.common_mut().dur_text); diff --git a/crates/kas-core/src/theme/traits.rs b/crates/kas-core/src/theme/traits.rs index 0380496d5..66bf6cad2 100644 --- a/crates/kas-core/src/theme/traits.rs +++ b/crates/kas-core/src/theme/traits.rs @@ -172,7 +172,9 @@ pub trait Theme: ThemeControl { cols: &'a ColorsLinear, ) -> Self::Draw<'a>; - /// Background colour + /// The window/scene clear color + /// + /// This is not used when the window is transparent. fn clear_color(&self) -> color::Rgba; } diff --git a/examples/clock.rs b/examples/clock.rs index 4a322f1f3..7ba6a412c 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -21,7 +21,6 @@ use kas::draw::{Draw, DrawRounded}; use kas::geom::{Offset, Quad, Rect, Vec2}; use kas::prelude::*; use kas::shell::ShellAssoc; -use kas::theme::ThemeControl; type Theme = kas::theme::FlatTheme; type Shell = kas::shell::DefaultShell; @@ -185,10 +184,6 @@ impl_scope! { fn main() -> kas::shell::Result<()> { env_logger::init(); - let mut theme = Theme::new(); - let mut cols = theme.get_colors().clone(); - cols.background = Rgba::ga(0.0, 0.0); - let _ = theme.set_colors("transparent".to_string(), cols); - + let theme = Theme::new(); Shell::new(theme)?.with(Clock::new())?.run() } From 81ff153978a2a195c7c35030050c224e1a62d319 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 5 Feb 2023 18:30:56 +0000 Subject: [PATCH 11/25] MarkButton: highlight on hover --- crates/kas-core/src/theme/simple_theme.rs | 82 +++++++++++++---------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/crates/kas-core/src/theme/simple_theme.rs b/crates/kas-core/src/theme/simple_theme.rs index 340c4c31b..de941bbcc 100644 --- a/crates/kas-core/src/theme/simple_theme.rs +++ b/crates/kas-core/src/theme/simple_theme.rs @@ -238,6 +238,42 @@ where self.draw.rect(line, col); } } + + fn draw_mark(&mut self, rect: Rect, style: MarkStyle, col: Rgba) { + match style { + MarkStyle::Point(dir) => { + let size = match dir.is_horizontal() { + true => Size(self.w.dims.mark / 2, self.w.dims.mark), + false => Size(self.w.dims.mark, self.w.dims.mark / 2), + }; + let offset = Offset::conv((rect.size - size) / 2); + let q = Quad::conv(Rect::new(rect.pos + offset, size)); + + let (p1, p2, p3); + if dir.is_horizontal() { + let (mut x1, mut x2) = (q.a.0, q.b.0); + if dir.is_reversed() { + std::mem::swap(&mut x1, &mut x2); + } + p1 = Vec2(x1, q.a.1); + p2 = Vec2(x2, 0.5 * (q.a.1 + q.b.1)); + p3 = Vec2(x1, q.b.1); + } else { + let (mut y1, mut y2) = (q.a.1, q.b.1); + if dir.is_reversed() { + std::mem::swap(&mut y1, &mut y2); + } + p1 = Vec2(q.a.0, y1); + p2 = Vec2(0.5 * (q.a.0 + q.b.0), y2); + p3 = Vec2(q.b.0, y1); + }; + + let f = 0.5 * self.w.dims.mark_line; + self.draw.rounded_line(p1, p2, f, col); + self.draw.rounded_line(p2, p3, f, col); + } + } + } } impl<'a, DS: DrawSharedImpl> ThemeDraw for DrawHandle<'a, DS> @@ -464,45 +500,19 @@ where fn mark(&mut self, id: &WidgetId, rect: Rect, style: MarkStyle) { let col = if self.ev.is_disabled(id) { self.cols.text_disabled - } else if self.ev.is_hovered(id) { - self.cols.accent } else { - self.cols.text + let is_depressed = self.ev.is_depressed(id); + if self.ev.is_hovered(id) || is_depressed { + self.draw.rect(rect.cast(), self.cols.accent_soft); + } + if is_depressed { + self.cols.accent + } else { + self.cols.text + } }; - match style { - MarkStyle::Point(dir) => { - let size = match dir.is_horizontal() { - true => Size(self.w.dims.mark / 2, self.w.dims.mark), - false => Size(self.w.dims.mark, self.w.dims.mark / 2), - }; - let offset = Offset::conv((rect.size - size) / 2); - let q = Quad::conv(Rect::new(rect.pos + offset, size)); - - let (p1, p2, p3); - if dir.is_horizontal() { - let (mut x1, mut x2) = (q.a.0, q.b.0); - if dir.is_reversed() { - std::mem::swap(&mut x1, &mut x2); - } - p1 = Vec2(x1, q.a.1); - p2 = Vec2(x2, 0.5 * (q.a.1 + q.b.1)); - p3 = Vec2(x1, q.b.1); - } else { - let (mut y1, mut y2) = (q.a.1, q.b.1); - if dir.is_reversed() { - std::mem::swap(&mut y1, &mut y2); - } - p1 = Vec2(q.a.0, y1); - p2 = Vec2(0.5 * (q.a.0 + q.b.0), y2); - p3 = Vec2(q.b.0, y1); - }; - - let f = 0.5 * self.w.dims.mark_line; - self.draw.rounded_line(p1, p2, f, col); - self.draw.rounded_line(p2, p3, f, col); - } - } + self.draw_mark(rect, style, col); } fn scroll_bar( From 6698c37cfcb2a13d900274ccd2c79a912f7715c3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 08:42:48 +0000 Subject: [PATCH 12/25] Add a basic TitleBar --- crates/kas-core/src/theme/dimensions.rs | 4 ++ crates/kas-core/src/theme/simple_theme.rs | 11 +++++ crates/kas-core/src/theme/style.rs | 2 + crates/kas-widgets/src/lib.rs | 1 + crates/kas-widgets/src/title_bar.rs | 60 +++++++++++++++++++++++ 5 files changed, 78 insertions(+) create mode 100644 crates/kas-widgets/src/title_bar.rs diff --git a/crates/kas-core/src/theme/dimensions.rs b/crates/kas-core/src/theme/dimensions.rs index 4bb8fb91b..b382e6316 100644 --- a/crates/kas-core/src/theme/dimensions.rs +++ b/crates/kas-core/src/theme/dimensions.rs @@ -235,6 +235,10 @@ impl ThemeSize for Window { }; return SizeRules::fixed_splat(w, self.dims.m_tiny); } + Feature::Mark(MarkStyle::X) => { + let w = self.dims.mark + i32::conv_ceil(self.dims.mark_line); + return SizeRules::fixed_splat(w, self.dims.m_tiny); + } Feature::CheckBox | Feature::RadioBox => { return SizeRules::fixed_splat(self.dims.check_box, self.dims.m_small); } diff --git a/crates/kas-core/src/theme/simple_theme.rs b/crates/kas-core/src/theme/simple_theme.rs index de941bbcc..50f419dac 100644 --- a/crates/kas-core/src/theme/simple_theme.rs +++ b/crates/kas-core/src/theme/simple_theme.rs @@ -272,6 +272,17 @@ where self.draw.rounded_line(p1, p2, f, col); self.draw.rounded_line(p2, p3, f, col); } + MarkStyle::X => { + let size = Size::splat(self.w.dims.mark); + let offset = Offset::conv((rect.size - size) / 2); + let q = Quad::conv(Rect::new(rect.pos + offset, size)); + + let f = 0.5 * self.w.dims.mark_line; + self.draw.rounded_line(q.a, q.b, f, col); + let c = Vec2(q.a.0, q.b.1); + let d = Vec2(q.b.0, q.a.1); + self.draw.rounded_line(c, d, f, col); + } } } } diff --git a/crates/kas-core/src/theme/style.rs b/crates/kas-core/src/theme/style.rs index 9c420bb61..57cf73a8d 100644 --- a/crates/kas-core/src/theme/style.rs +++ b/crates/kas-core/src/theme/style.rs @@ -49,6 +49,8 @@ pub enum MarginStyle { pub enum MarkStyle { /// An arrowhead/angle-bracket/triangle pointing in the given direction Point(Direction), + /// A cross rotated 45° + X, } /// Various features which may be sized and drawn diff --git a/crates/kas-widgets/src/lib.rs b/crates/kas-widgets/src/lib.rs index 6eb7c850c..b4d751c5a 100644 --- a/crates/kas-widgets/src/lib.rs +++ b/crates/kas-widgets/src/lib.rs @@ -73,6 +73,7 @@ mod spinner; mod splitter; mod stack; mod tab_stack; +mod title_bar; pub mod adapter; diff --git a/crates/kas-widgets/src/title_bar.rs b/crates/kas-widgets/src/title_bar.rs new file mode 100644 index 000000000..909b00f33 --- /dev/null +++ b/crates/kas-widgets/src/title_bar.rs @@ -0,0 +1,60 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License in the LICENSE-APACHE file or at: +// https://www.apache.org/licenses/LICENSE-2.0 + +//! Title bar + +use super::Label; +use super::MarkButton; +use kas::prelude::*; +use kas::theme::MarkStyle; + +#[derive(Copy, Clone, Debug)] +enum TitleBarButton { + Minimize, + Maximize, + Close, +} + +impl_scope! { + /// A window's title bar (part of decoration) + #[derive(Clone, Debug, Default)] + #[widget{ + layout = row: [ + // self.icon, + self.title, + MarkButton::new(MarkStyle::Point(Direction::Down), TitleBarButton::Minimize), + MarkButton::new(MarkStyle::Point(Direction::Up), TitleBarButton::Maximize), + MarkButton::new(MarkStyle::X, TitleBarButton::Close), + ]; + }] + pub struct TitleBar { + core: widget_core!(), + #[widget] + title: Label, + } + + impl Self { + /// Construct a title bar + #[inline] + pub fn new(title: String) -> Self { + TitleBar { + core: Default::default(), + title: Label::new(title), + } + } + } + + impl Widget for Self { + fn handle_message(&mut self, mgr: &mut EventMgr) { + if let Some(msg) = mgr.try_pop() { + match msg { + TitleBarButton::Minimize => todo!(), + TitleBarButton::Maximize => todo!(), + TitleBarButton::Close => mgr.send_action(Action::CLOSE), + } + } + } + } +} From 8d8fc8af3b115724f2a85c822ff8f176349a5e0d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 08:47:08 +0000 Subject: [PATCH 13/25] Move TitleBar to kas-core --- crates/kas-core/src/lib.rs | 1 + crates/kas-core/src/title_bar.rs | 158 ++++++++++++++++++++++++++++ crates/kas-widgets/src/lib.rs | 1 - crates/kas-widgets/src/title_bar.rs | 60 ----------- 4 files changed, 159 insertions(+), 61 deletions(-) create mode 100644 crates/kas-core/src/title_bar.rs delete mode 100644 crates/kas-widgets/src/title_bar.rs diff --git a/crates/kas-core/src/lib.rs b/crates/kas-core/src/lib.rs index 16e73b086..c31190655 100644 --- a/crates/kas-core/src/lib.rs +++ b/crates/kas-core/src/lib.rs @@ -29,6 +29,7 @@ mod action; mod core; mod erased; mod root; +mod title_bar; pub use crate::core::*; pub use action::Action; diff --git a/crates/kas-core/src/title_bar.rs b/crates/kas-core/src/title_bar.rs new file mode 100644 index 000000000..102273616 --- /dev/null +++ b/crates/kas-core/src/title_bar.rs @@ -0,0 +1,158 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License in the LICENSE-APACHE file or at: +// https://www.apache.org/licenses/LICENSE-2.0 + +//! Title bar +//! +//! Note: due to definition in kas-core, some widgets must be duplicated. + +use crate::event::ConfigMgr; +use crate::geom::Rect; +use crate::layout::{Align, AxisInfo, SizeRules}; +use crate::text::Text; +use crate::theme::{DrawMgr, SizeMgr, TextClass}; +use crate::{Layout, WidgetCore}; +use kas::prelude::*; +use kas::theme::MarkStyle; +use kas_macros::impl_scope; +use std::fmt::Debug; + +impl_scope! { + /// A simple label + #[derive(Clone, Debug, Default)] + #[widget] + pub struct Label { + core: widget_core!(), + label: Text, + } + + impl Self { + /// Construct from `label` + #[inline] + fn new(label: String) -> Self { + Label { + core: Default::default(), + label: Text::new(label), + } + } + + /// Text class + pub const CLASS: TextClass = TextClass::Label(false); + } + + impl Layout for Self { + #[inline] + fn size_rules(&mut self, size_mgr: SizeMgr, mut axis: AxisInfo) -> SizeRules { + axis.set_default_align_hv(Align::Center, Align::Center); + size_mgr.text_rules(&mut self.label, Self::CLASS, axis) + } + + fn set_rect(&mut self, mgr: &mut ConfigMgr, rect: Rect) { + self.core.rect = rect; + mgr.text_set_size(&mut self.label, Self::CLASS, rect.size, None); + } + + fn draw(&mut self, mut draw: DrawMgr) { + draw.text(self.rect(), &self.label, Self::CLASS); + } + } +} + +impl_scope! { + /// A mark which is also a button + /// + /// This button is not keyboard navigable; only mouse/touch interactive. + /// + /// Uses stretch policy [`Stretch::Low`]. + #[derive(Clone, Debug)] + #[widget { + hover_highlight = true; + }] + pub struct MarkButton { + core: widget_core!(), + style: MarkStyle, + msg: M, + } + + impl Self { + /// Construct + /// + /// A clone of `msg` is sent as a message on click. + pub fn new(style: MarkStyle, msg: M) -> Self { + MarkButton { + core: Default::default(), + style, + msg, + } + } + } + + impl Layout for Self { + fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules { + mgr.feature(self.style.into(), axis) + } + + fn draw(&mut self, mut draw: DrawMgr) { + draw.mark(self.core.rect, self.style); + } + } + + impl Widget for Self { + fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { + event.on_activate(mgr, self.id(), |mgr| { + mgr.push(self.msg.clone()); + Response::Used + }) + } + } +} + +#[derive(Copy, Clone, Debug)] +enum TitleBarButton { + Minimize, + Maximize, + Close, +} + +impl_scope! { + /// A window's title bar (part of decoration) + #[derive(Clone, Debug, Default)] + #[widget{ + layout = row: [ + // self.icon, + self.title, + MarkButton::new(MarkStyle::Point(Direction::Down), TitleBarButton::Minimize), + MarkButton::new(MarkStyle::Point(Direction::Up), TitleBarButton::Maximize), + MarkButton::new(MarkStyle::X, TitleBarButton::Close), + ]; + }] + pub struct TitleBar { + core: widget_core!(), + #[widget] + title: Label, + } + + impl Self { + /// Construct a title bar + #[inline] + pub fn new(title: String) -> Self { + TitleBar { + core: Default::default(), + title: Label::new(title), + } + } + } + + impl Widget for Self { + fn handle_message(&mut self, mgr: &mut EventMgr) { + if let Some(msg) = mgr.try_pop() { + match msg { + TitleBarButton::Minimize => todo!(), + TitleBarButton::Maximize => todo!(), + TitleBarButton::Close => mgr.send_action(Action::CLOSE), + } + } + } + } +} diff --git a/crates/kas-widgets/src/lib.rs b/crates/kas-widgets/src/lib.rs index b4d751c5a..6eb7c850c 100644 --- a/crates/kas-widgets/src/lib.rs +++ b/crates/kas-widgets/src/lib.rs @@ -73,7 +73,6 @@ mod spinner; mod splitter; mod stack; mod tab_stack; -mod title_bar; pub mod adapter; diff --git a/crates/kas-widgets/src/title_bar.rs b/crates/kas-widgets/src/title_bar.rs deleted file mode 100644 index 909b00f33..000000000 --- a/crates/kas-widgets/src/title_bar.rs +++ /dev/null @@ -1,60 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License in the LICENSE-APACHE file or at: -// https://www.apache.org/licenses/LICENSE-2.0 - -//! Title bar - -use super::Label; -use super::MarkButton; -use kas::prelude::*; -use kas::theme::MarkStyle; - -#[derive(Copy, Clone, Debug)] -enum TitleBarButton { - Minimize, - Maximize, - Close, -} - -impl_scope! { - /// A window's title bar (part of decoration) - #[derive(Clone, Debug, Default)] - #[widget{ - layout = row: [ - // self.icon, - self.title, - MarkButton::new(MarkStyle::Point(Direction::Down), TitleBarButton::Minimize), - MarkButton::new(MarkStyle::Point(Direction::Up), TitleBarButton::Maximize), - MarkButton::new(MarkStyle::X, TitleBarButton::Close), - ]; - }] - pub struct TitleBar { - core: widget_core!(), - #[widget] - title: Label, - } - - impl Self { - /// Construct a title bar - #[inline] - pub fn new(title: String) -> Self { - TitleBar { - core: Default::default(), - title: Label::new(title), - } - } - } - - impl Widget for Self { - fn handle_message(&mut self, mgr: &mut EventMgr) { - if let Some(msg) = mgr.try_pop() { - match msg { - TitleBarButton::Minimize => todo!(), - TitleBarButton::Maximize => todo!(), - TitleBarButton::Close => mgr.send_action(Action::CLOSE), - } - } - } - } -} From b45b05251e145df96c48b46935dc7a1e44097342 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 09:35:36 +0000 Subject: [PATCH 14/25] Add a title bar to RootWidget Visually functional but crude; close button works. --- crates/kas-core/src/root.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index 4ff8cb951..cf39de8e1 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -10,6 +10,7 @@ use crate::event::{ConfigMgr, EventMgr, Scroll}; use crate::geom::{Coord, Offset, Rect, Size}; use crate::layout::{self, AxisInfo, SizeRules}; use crate::theme::{DrawMgr, FrameStyle, SizeMgr}; +use crate::title_bar::TitleBar; use crate::{Action, Decorations, Layout, Widget, WidgetExt, WidgetId, Window, WindowId}; use kas_macros::impl_scope; use smallvec::SmallVec; @@ -23,7 +24,10 @@ impl_scope! { pub struct RootWidget { core: widget_core!(), #[widget] + title_bar: TitleBar, + #[widget] w: Box, + bar_h: i32, dec_offset: Offset, dec_size: Size, popups: SmallVec<[(WindowId, kas::Popup, Offset); 16]>, @@ -32,8 +36,19 @@ impl_scope! { impl Layout for RootWidget { #[inline] fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules { - let inner = self.w.size_rules(size_mgr.re(), axis); - if matches!(self.w.decorations(), Decorations::Border | Decorations::Toolkit) { + let mut inner = self.w.size_rules(size_mgr.re(), axis); + let decs = self.w.decorations(); + self.bar_h = 0; + if matches!(decs, Decorations::Toolkit) { + let bar = self.title_bar.size_rules(size_mgr.re(), axis); + if axis.is_horizontal() { + inner.max_with(bar); + } else { + inner.append(bar); + self.bar_h = bar.min_size(); + } + } + if matches!(decs, Decorations::Border | Decorations::Toolkit) { let frame = size_mgr.frame(FrameStyle::Window, axis); let (rules, offset, size) = frame.surround(inner); self.dec_offset.set_component(axis, offset); @@ -49,6 +64,12 @@ impl_scope! { self.core.rect = rect; rect.pos += self.dec_offset; rect.size -= self.dec_size; + if self.bar_h > 0 { + let bar_size = Size(rect.size.0, self.bar_h); + self.title_bar.set_rect(mgr, Rect::new(rect.pos, bar_size)); + rect.pos.1 += self.bar_h; + rect.size -= Size(0, self.bar_h); + } self.w.set_rect(mgr, rect); } @@ -65,12 +86,17 @@ impl_scope! { return Some(id); } } - self.w.find_id(coord).or_else(|| Some(self.id())) + self.title_bar.find_id(coord) + .or_else(|| self.w.find_id(coord)) + .or_else(|| Some(self.id())) } fn draw(&mut self, mut draw: DrawMgr) { if self.dec_size != Size::ZERO { draw.frame(self.core.rect, FrameStyle::Window, Default::default()); + if self.bar_h > 0 { + draw.recurse(&mut self.title_bar); + } } draw.recurse(&mut self.w); for (_, popup, translation) in &self.popups { @@ -136,7 +162,9 @@ impl RootWidget { pub fn new(w: Box) -> RootWidget { RootWidget { core: Default::default(), + title_bar: TitleBar::new(w.title().to_string()), w, + bar_h: 0, dec_offset: Default::default(), dec_size: Default::default(), popups: Default::default(), From 1f7ce6fb2f9b677f0707ce7532609848eba16333 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 10:00:26 +0000 Subject: [PATCH 15/25] Make min/maximize buttons functional --- crates/kas-core/src/event/manager/mgr_pub.rs | 7 +++++++ crates/kas-core/src/shell/common.rs | 5 +++++ crates/kas-core/src/shell/window.rs | 5 +++++ crates/kas-core/src/title_bar.rs | 13 +++++++++++-- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/kas-core/src/event/manager/mgr_pub.rs b/crates/kas-core/src/event/manager/mgr_pub.rs index 2dce15c11..ff4dbb809 100644 --- a/crates/kas-core/src/event/manager/mgr_pub.rs +++ b/crates/kas-core/src/event/manager/mgr_pub.rs @@ -802,6 +802,13 @@ impl<'a> EventMgr<'a> { result.expect("ShellWindow::size_and_draw_shared impl failed to call function argument") } + /// Directly access Winit Window + /// + /// This is a temporary API, allowing e.g. to minimize the window. + pub fn winit_window(&self) -> Option<&winit::window::Window> { + self.shell.winit_window() + } + /// Grab "press" events for `source` (a mouse or finger) /// /// When a "press" source is "grabbed", events for this source will be sent diff --git a/crates/kas-core/src/shell/common.rs b/crates/kas-core/src/shell/common.rs index b0f32d71e..3d2864098 100644 --- a/crates/kas-core/src/shell/common.rs +++ b/crates/kas-core/src/shell/common.rs @@ -161,6 +161,11 @@ pub(crate) trait ShellWindow { /// Set the mouse cursor fn set_cursor_icon(&mut self, icon: CursorIcon); + /// Directly access Winit Window + /// + /// This is a temporary API, allowing e.g. to minimize the window. + fn winit_window(&self) -> Option<&winit::window::Window>; + /// Access a Waker fn waker(&self) -> &std::task::Waker; } diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 8991ccffd..16489b907 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -520,6 +520,11 @@ where } } + #[inline] + fn winit_window(&self) -> Option<&winit::window::Window> { + self.window + } + #[inline] fn waker(&self) -> &std::task::Waker { &self.shared.waker diff --git a/crates/kas-core/src/title_bar.rs b/crates/kas-core/src/title_bar.rs index 102273616..3cfdc15a7 100644 --- a/crates/kas-core/src/title_bar.rs +++ b/crates/kas-core/src/title_bar.rs @@ -148,8 +148,17 @@ impl_scope! { fn handle_message(&mut self, mgr: &mut EventMgr) { if let Some(msg) = mgr.try_pop() { match msg { - TitleBarButton::Minimize => todo!(), - TitleBarButton::Maximize => todo!(), + TitleBarButton::Minimize => { + mgr.winit_window().map(|w| { + // TODO: supported in winit 0.28: + // let is_minimized = w.is_minimized().unwrap_or(false); + let is_minimized = false; + w.set_minimized(!is_minimized); + }); + } + TitleBarButton::Maximize => { + mgr.winit_window().map(|w| w.set_maximized(!w.is_maximized())); + } TitleBarButton::Close => mgr.send_action(Action::CLOSE), } } From 009ed36fb07958e8d18106b3f482577c45fc155f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 12:07:06 +0000 Subject: [PATCH 16/25] Add kas_core::shell::PlatformWrapper as backend abstraction --- crates/kas-core/src/shell/mod.rs | 1 + crates/kas-core/src/shell/shared.rs | 9 ++- crates/kas-core/src/shell/shell.rs | 100 +++++++++++++++------------- 3 files changed, 57 insertions(+), 53 deletions(-) diff --git a/crates/kas-core/src/shell/mod.rs b/crates/kas-core/src/shell/mod.rs index fe853664d..8107ec427 100644 --- a/crates/kas-core/src/shell/mod.rs +++ b/crates/kas-core/src/shell/mod.rs @@ -13,6 +13,7 @@ mod common; #[cfg(feature = "winit")] use event_loop::Loop as EventLoop; #[cfg(feature = "winit")] use shared::SharedState; +#[cfg(feature = "winit")] use shell::PlatformWrapper; #[cfg(feature = "winit")] use window::Window; pub(crate) use common::ShellWindow; diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index 5661b75c9..f2150578e 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -40,13 +40,12 @@ where T::Window: kas::theme::Window, { /// Construct - pub fn new( + pub(super) fn new( + pw: super::PlatformWrapper, draw_shared: S::Shared, mut theme: T, options: Options, config: SharedRc, - scale_factor: f64, - waker: Waker, ) -> Result { let mut draw = kas::draw::SharedState::new(draw_shared); theme.init(&mut draw); @@ -58,8 +57,8 @@ where theme, config, pending: vec![], - scale_factor, - waker, + scale_factor: pw.guess_scale_factor(), + waker: pw.create_waker(), window_id: 0, options, }) diff --git a/crates/kas-core/src/shell/shell.rs b/crates/kas-core/src/shell/shell.rs index 2835a85d6..fba882f5d 100644 --- a/crates/kas-core/src/shell/shell.rs +++ b/crates/kas-core/src/shell/shell.rs @@ -13,7 +13,7 @@ use crate::model::SharedRc; use crate::theme::{self, Theme, ThemeConfig}; use crate::util::warn_about_error; use crate::WindowId; -use winit::event_loop::{EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget}; +use winit::event_loop::{EventLoop, EventLoopBuilder, EventLoopProxy}; /// The KAS shell /// @@ -113,9 +113,8 @@ where let windows = vec![]; let draw_shared = graphical_shell.into().build(theme.config().raster())?; - let scale_factor = find_scale_factor(&el); - let waker = create_waker(Proxy(el.create_proxy())); - let shared = SharedState::new(draw_shared, theme, options, config, scale_factor, waker)?; + let pw = PlatformWrapper(&el); + let shared = SharedState::new(pw, draw_shared, theme, options, config)?; Ok(Shell { el, @@ -198,53 +197,58 @@ where } } -fn find_scale_factor(el: &EventLoopWindowTarget) -> f64 { - if let Some(mon) = el.primary_monitor() { - return mon.scale_factor(); +pub(super) struct PlatformWrapper<'a>(&'a EventLoop); +impl<'a> PlatformWrapper<'a> { + /// Guess scale factor of first window + pub(super) fn guess_scale_factor(&self) -> f64 { + if let Some(mon) = self.0.primary_monitor() { + return mon.scale_factor(); + } + if let Some(mon) = self.0.available_monitors().next() { + return mon.scale_factor(); + } + 1.0 } - if let Some(mon) = el.available_monitors().next() { - return mon.scale_factor(); - } - 1.0 -} -/// Create a waker -/// -/// This waker may be used by a [`Future`](std::future::Future) to revive -/// event handling. -fn create_waker(proxy: Proxy) -> std::task::Waker { - use std::sync::{Arc, Mutex}; - use std::task::{RawWaker, RawWakerVTable, Waker}; - - // NOTE: Proxy is Send but not Sync. Mutex is Sync for T: Send. - // We wrap with Arc which is a Sync type supporting Clone and into_raw. - type Data = Mutex; - let a: Arc = Arc::new(Mutex::new(proxy)); - let data = Arc::into_raw(a); - - const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); - - unsafe fn clone(data: *const ()) -> RawWaker { - let a = Arc::from_raw(data as *const Data); - let c = Arc::into_raw(a.clone()); - let _do_not_drop = Arc::into_raw(a); - RawWaker::new(c as *const (), &VTABLE) - } - unsafe fn wake(data: *const ()) { - let a = Arc::from_raw(data as *const Data); - a.lock().unwrap().wake_async(); - } - unsafe fn wake_by_ref(data: *const ()) { - let a = Arc::from_raw(data as *const Data); - a.lock().unwrap().wake_async(); - let _do_not_drop = Arc::into_raw(a); - } - unsafe fn drop(data: *const ()) { - let _ = Arc::from_raw(data as *const Data); + /// Create a waker + /// + /// This waker may be used by a [`Future`](std::future::Future) to revive + /// event handling. + pub(super) fn create_waker(&self) -> std::task::Waker { + use std::sync::{Arc, Mutex}; + use std::task::{RawWaker, RawWakerVTable, Waker}; + + // NOTE: Proxy is Send but not Sync. Mutex is Sync for T: Send. + // We wrap with Arc which is a Sync type supporting Clone and into_raw. + type Data = Mutex; + let proxy = Proxy(self.0.create_proxy()); + let a: Arc = Arc::new(Mutex::new(proxy)); + let data = Arc::into_raw(a); + + const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); + + unsafe fn clone(data: *const ()) -> RawWaker { + let a = Arc::from_raw(data as *const Data); + let c = Arc::into_raw(a.clone()); + let _do_not_drop = Arc::into_raw(a); + RawWaker::new(c as *const (), &VTABLE) + } + unsafe fn wake(data: *const ()) { + let a = Arc::from_raw(data as *const Data); + a.lock().unwrap().wake_async(); + } + unsafe fn wake_by_ref(data: *const ()) { + let a = Arc::from_raw(data as *const Data); + a.lock().unwrap().wake_async(); + let _do_not_drop = Arc::into_raw(a); + } + unsafe fn drop(data: *const ()) { + let _ = Arc::from_raw(data as *const Data); + } + + let raw_waker = RawWaker::new(data as *const (), &VTABLE); + unsafe { Waker::from_raw(raw_waker) } } - - let raw_waker = RawWaker::new(data as *const (), &VTABLE); - unsafe { Waker::from_raw(raw_waker) } } /// A proxy allowing control of a [`Shell`] from another thread. From 791a5b9bdc105bdb1c3a41ad1843d26c8f13a90d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 12:15:03 +0000 Subject: [PATCH 17/25] Add Platform enumeration --- crates/kas-core/Cargo.toml | 1 + crates/kas-core/src/shell/common.rs | 135 ++++++++++++++++++++++++++++ crates/kas-core/src/shell/mod.rs | 2 +- crates/kas-core/src/shell/shared.rs | 4 +- crates/kas-core/src/shell/shell.rs | 40 ++++++++- crates/kas-core/src/shell/window.rs | 20 ++--- 6 files changed, 185 insertions(+), 17 deletions(-) diff --git a/crates/kas-core/Cargo.toml b/crates/kas-core/Cargo.toml index c805c202d..38bc44717 100644 --- a/crates/kas-core/Cargo.toml +++ b/crates/kas-core/Cargo.toml @@ -80,6 +80,7 @@ dark-light = { version = "0.2.2", optional = true } raw-window-handle = "0.5.0" window_clipboard = { version = "0.2.0", optional = true } async-global-executor = { version = "2.3.1", optional = true } +cfg-if = "1.0.0" [dependencies.kas-macros] version = "0.12.0" diff --git a/crates/kas-core/src/shell/common.rs b/crates/kas-core/src/shell/common.rs index 3d2864098..de3881625 100644 --- a/crates/kas-core/src/shell/common.rs +++ b/crates/kas-core/src/shell/common.rs @@ -41,6 +41,138 @@ pub enum Error { /// A `Result` type representing `T` or [`enum@Error`] pub type Result = std::result::Result; +/// Enumeration of platforms +/// +/// Each option is compile-time enabled only if that platform is possible. +/// Methods like [`Self::is_wayland`] are available on all platforms. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum Platform { + #[cfg(target_os = "android")] + Android, + #[cfg(target_os = "ios")] + IOS, + #[cfg(target_os = "macos")] + MacOS, + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + Wayland, + #[cfg(target_arch = "wasm32")] + Web, + #[cfg(target_os = "windows")] + Windows, + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + X11, +} + +impl Platform { + /// True if the platform is Android + pub fn is_android(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(target_os = "android")] { + true + } else { + false + } + } + } + + /// True if the platform is IOS + pub fn is_ios(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(target_os = "ios")] { + true + } else { + false + } + } + } + + /// True if the platform is MacOS + pub fn is_macos(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(target_os = "macos")] { + true + } else { + false + } + } + } + + /// True if the platform is Wayland + pub fn is_wayland(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] { + match self { + Platform::Wayland => true, + _ => false, + } + } else { + false + } + } + } + + /// True if the platform is Web + pub fn is_web(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + true + } else { + false + } + } + } + + /// True if the platform is Windows + pub fn is_windows(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(target_os = "windows")] { + true + } else { + false + } + } + } + + /// True if the platform is X11 + pub fn is_x11(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] { + match self { + Platform::X11 => true, + _ => false, + } + } else { + false + } + } + } +} + /// API for the graphical implementation of a shell /// /// See also [`Shell`](super::Shell). @@ -161,6 +293,9 @@ pub(crate) trait ShellWindow { /// Set the mouse cursor fn set_cursor_icon(&mut self, icon: CursorIcon); + /// Get the platform + fn platform(&self) -> Platform; + /// Directly access Winit Window /// /// This is a temporary API, allowing e.g. to minimize the window. diff --git a/crates/kas-core/src/shell/mod.rs b/crates/kas-core/src/shell/mod.rs index 8107ec427..77c6596a8 100644 --- a/crates/kas-core/src/shell/mod.rs +++ b/crates/kas-core/src/shell/mod.rs @@ -18,7 +18,7 @@ mod common; pub(crate) use common::ShellWindow; #[cfg(feature = "winit")] pub use common::WindowSurface; -pub use common::{Error, GraphicalShell, Result}; +pub use common::{Error, GraphicalShell, Platform, Result}; #[cfg(feature = "winit")] pub use shell::{ClosedError, Proxy, Shell, ShellAssoc}; #[cfg(feature = "winit")] diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index f2150578e..fcc3adb26 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -8,7 +8,7 @@ use std::num::NonZeroU32; use std::task::Waker; -use super::{PendingAction, WindowSurface}; +use super::{PendingAction, Platform, WindowSurface}; use kas::config::Options; use kas::event::UpdateId; use kas::model::SharedRc; @@ -22,6 +22,7 @@ use window_clipboard::Clipboard; /// State shared between windows pub struct SharedState { + pub(super) platform: Platform, #[cfg(feature = "clipboard")] clipboard: Option, pub(super) draw: draw::SharedState, @@ -51,6 +52,7 @@ where theme.init(&mut draw); Ok(SharedState { + platform: pw.platform(), #[cfg(feature = "clipboard")] clipboard: None, draw, diff --git a/crates/kas-core/src/shell/shell.rs b/crates/kas-core/src/shell/shell.rs index fba882f5d..80da74d75 100644 --- a/crates/kas-core/src/shell/shell.rs +++ b/crates/kas-core/src/shell/shell.rs @@ -5,7 +5,7 @@ //! [`Shell`] and supporting elements -use super::{GraphicalShell, ProxyAction, Result, SharedState, Window}; +use super::{GraphicalShell, Platform, ProxyAction, Result, SharedState, Window}; use crate::config::Options; use crate::draw::{DrawImpl, DrawShared, DrawSharedImpl}; use crate::event::{self, UpdateId}; @@ -199,6 +199,44 @@ where pub(super) struct PlatformWrapper<'a>(&'a EventLoop); impl<'a> PlatformWrapper<'a> { + /// Get platform + pub(super) fn platform(&self) -> Platform { + // Logic copied from winit::platform_impl module. + + #[cfg(target_os = "windows")] + return Platform::Windows; + + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + use winit::platform::unix::EventLoopWindowTargetExtUnix; + return if self.0.is_wayland() { + Platform::Wayland + } else { + Platform::X11 + }; + } + + #[cfg(target_os = "macos")] + return Platform::MacOS; + + #[cfg(target_os = "android")] + return Platform::Android; + + #[cfg(target_os = "ios")] + return Platform::IOS; + + #[cfg(target_arch = "wasm32")] + return Platform::Web; + + // Otherwise platform is unsupported! + } + /// Guess scale factor of first window pub(super) fn guess_scale_factor(&self) -> f64 { if let Some(mon) = self.0.primary_monitor() { diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 16489b907..13b1c2433 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -5,7 +5,7 @@ //! Window types -use super::{PendingAction, ProxyAction, SharedState, ShellWindow, WindowSurface}; +use super::{PendingAction, Platform, ProxyAction, SharedState, ShellWindow, WindowSurface}; use kas::cast::Cast; use kas::draw::{color::Rgba, AnimationState, DrawShared}; use kas::event::{ConfigMgr, CursorIcon, EventState, UpdateId}; @@ -49,19 +49,7 @@ impl> Window { let mut widget = kas::RootWidget::new(widget); // Wayland only supports windows constructed via logical size - #[allow(unused_assignments, unused_mut)] - let mut use_logical_size = false; - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - { - use winit::platform::unix::EventLoopWindowTargetExtUnix; - use_logical_size = elwt.is_wayland(); - } + let use_logical_size = shared.platform.is_wayland(); let scale_factor = if use_logical_size { 1.0 @@ -520,6 +508,10 @@ where } } + fn platform(&self) -> Platform { + self.shared.platform + } + #[inline] fn winit_window(&self) -> Option<&winit::window::Window> { self.window From 87d923b31c8696a45458e3f3c9bfd10e1b3e43ef Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 15:29:00 +0000 Subject: [PATCH 18/25] Add ConfigMgr::platform --- crates/kas-core/src/draw/draw_shared.rs | 14 ++++++++++++-- crates/kas-core/src/event/manager/config_mgr.rs | 6 ++++++ crates/kas-core/src/shell/shared.rs | 5 +++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/crates/kas-core/src/draw/draw_shared.rs b/crates/kas-core/src/draw/draw_shared.rs index 1adb3f11d..ce252f9c4 100644 --- a/crates/kas-core/src/draw/draw_shared.rs +++ b/crates/kas-core/src/draw/draw_shared.rs @@ -9,6 +9,7 @@ use super::color::Rgba; use super::{DrawImpl, PassId}; use crate::cast::Cast; use crate::geom::{Quad, Rect, Size}; +use crate::shell::Platform; use crate::text::{Effect, TextDisplay}; use std::any::Any; use std::num::NonZeroU32; @@ -74,14 +75,15 @@ pub struct AllocError; pub struct SharedState { /// The shell's [`DrawSharedImpl`] object pub draw: DS, + platform: Platform, } #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] impl SharedState { /// Construct (this is only called by the shell) - pub fn new(draw: DS) -> Self { - SharedState { draw } + pub fn new(draw: DS, platform: Platform) -> Self { + SharedState { draw, platform } } } @@ -89,6 +91,9 @@ impl SharedState { /// /// All methods concern management of resources for drawing. pub trait DrawShared { + /// Get the platform + fn platform(&self) -> Platform; + /// Allocate an image /// /// Use [`SharedState::image_upload`] to set contents of the new image. @@ -115,6 +120,11 @@ pub trait DrawShared { } impl DrawShared for SharedState { + #[inline] + fn platform(&self) -> Platform { + self.platform + } + #[inline] fn image_alloc(&mut self, size: (u32, u32)) -> Result { self.draw diff --git a/crates/kas-core/src/event/manager/config_mgr.rs b/crates/kas-core/src/event/manager/config_mgr.rs index cbc4869ec..3541cc595 100644 --- a/crates/kas-core/src/event/manager/config_mgr.rs +++ b/crates/kas-core/src/event/manager/config_mgr.rs @@ -10,6 +10,7 @@ use crate::draw::DrawShared; use crate::event::EventState; use crate::geom::{Rect, Size}; use crate::layout::AlignPair; +use crate::shell::Platform; use crate::text::TextApi; use crate::theme::{Feature, SizeMgr, TextClass, ThemeSize}; use crate::{Action, Widget, WidgetExt, WidgetId}; @@ -37,6 +38,11 @@ impl<'a> ConfigMgr<'a> { ConfigMgr { sh, ds, ev } } + /// Get the platform + pub fn platform(&self) -> Platform { + self.ds.platform() + } + /// Access a [`SizeMgr`] /// /// Warning: sizes are calculated using the window's current scale factor. diff --git a/crates/kas-core/src/shell/shared.rs b/crates/kas-core/src/shell/shared.rs index fcc3adb26..e6fcbdc10 100644 --- a/crates/kas-core/src/shell/shared.rs +++ b/crates/kas-core/src/shell/shared.rs @@ -48,11 +48,12 @@ where options: Options, config: SharedRc, ) -> Result { - let mut draw = kas::draw::SharedState::new(draw_shared); + let platform = pw.platform(); + let mut draw = kas::draw::SharedState::new(draw_shared, platform); theme.init(&mut draw); Ok(SharedState { - platform: pw.platform(), + platform, #[cfg(feature = "clipboard")] clipboard: None, draw, From 0112f692e04800e008bc3906569092e4aafd50a5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 15:31:25 +0000 Subject: [PATCH 19/25] Automatically use toolkit decorations instead of server on Wayland --- crates/kas-core/src/root.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index cf39de8e1..5ca7e8340 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -23,6 +23,7 @@ impl_scope! { #[widget] pub struct RootWidget { core: widget_core!(), + decorations: Decorations, #[widget] title_bar: TitleBar, #[widget] @@ -37,9 +38,9 @@ impl_scope! { #[inline] fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules { let mut inner = self.w.size_rules(size_mgr.re(), axis); - let decs = self.w.decorations(); + self.bar_h = 0; - if matches!(decs, Decorations::Toolkit) { + if matches!(self.decorations, Decorations::Toolkit) { let bar = self.title_bar.size_rules(size_mgr.re(), axis); if axis.is_horizontal() { inner.max_with(bar); @@ -48,7 +49,7 @@ impl_scope! { self.bar_h = bar.min_size(); } } - if matches!(decs, Decorations::Border | Decorations::Toolkit) { + if matches!(self.decorations, Decorations::Border | Decorations::Toolkit) { let frame = size_mgr.frame(FrameStyle::Window, axis); let (rules, offset, size) = frame.surround(inner); self.dec_offset.set_component(axis, offset); @@ -111,6 +112,16 @@ impl_scope! { } impl Widget for RootWidget { + fn configure(&mut self, mgr: &mut ConfigMgr) { + self.decorations = self.w.decorations(); + if mgr.platform().is_wayland() && self.decorations == Decorations::Server { + // Wayland's base protocol does not support server-side decorations + // TODO: Wayland has extensions for this; server-side is still + // usually preferred where supported (e.g. KDE). + self.decorations = Decorations::Toolkit; + } + } + fn handle_scroll(&mut self, mgr: &mut EventMgr, _: Scroll) { // Something was scrolled; update pop-up translations mgr.config_mgr(|mgr| self.resize_popups(mgr)); @@ -162,6 +173,7 @@ impl RootWidget { pub fn new(w: Box) -> RootWidget { RootWidget { core: Default::default(), + decorations: Decorations::None, title_bar: TitleBar::new(w.title().to_string()), w, bar_h: 0, From 0f985954a2268ced859baec43214e85764bc1548 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 15:43:21 +0000 Subject: [PATCH 20/25] Make x11, wayland optional; do not use winit/wayland-csd-adwaita --- Cargo.toml | 8 +++++++- crates/kas-core/Cargo.toml | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 858d3d9c9..c601de900 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ rustdoc-args = ["--cfg", "doc_cfg"] ######### meta / build features ######### # All recommended features for optimal experience -default = ["minimal", "view", "yaml", "image", "resvg", "clipboard", "markdown", "shaping", "dark-light", "spawn"] +default = ["minimal", "view", "yaml", "image", "resvg", "clipboard", "markdown", "shaping", "dark-light", "spawn", "wayland", "x11"] # Include only "required" features. # @@ -104,6 +104,12 @@ macros_log = ["kas-core/macros_log"] winit = ["kas-core/winit"] +# Support Wayland +wayland = ["kas-core/wayland"] + +# Support X11 +x11 = ["kas-core/x11"] + [dependencies] kas-core = { version = "0.12.1", path = "crates/kas-core" } kas-dylib = { version = "0.12.0", path = "crates/kas-dylib", optional = true } diff --git a/crates/kas-core/Cargo.toml b/crates/kas-core/Cargo.toml index 38bc44717..60676d3d0 100644 --- a/crates/kas-core/Cargo.toml +++ b/crates/kas-core/Cargo.toml @@ -55,6 +55,12 @@ macros_log = ["kas-macros/log"] # Enable winit support winit = ["dep:winit"] +# Support Wayland +wayland = ["winit?/wayland", "winit?/wayland-dlopen"] + +# Support X11 +x11 = ["winit?/x11"] + # Enable serde integration (mainly config read/write) serde = ["dep:serde", "kas-text/serde", "winit?/serde"] @@ -96,3 +102,4 @@ version = "0.5.0" # used in doc links # Provides translations for several winit types version = "0.27" optional = true +default-features = false From 3909515f999660ad957b53eb99c9c34b6f1a4889 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 16:04:21 +0000 Subject: [PATCH 21/25] Fix: winit without Wayland --- crates/kas-core/src/shell/shell.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/kas-core/src/shell/shell.rs b/crates/kas-core/src/shell/shell.rs index 80da74d75..6e6816745 100644 --- a/crates/kas-core/src/shell/shell.rs +++ b/crates/kas-core/src/shell/shell.rs @@ -214,12 +214,22 @@ impl<'a> PlatformWrapper<'a> { target_os = "openbsd" ))] { - use winit::platform::unix::EventLoopWindowTargetExtUnix; - return if self.0.is_wayland() { - Platform::Wayland - } else { - Platform::X11 - }; + cfg_if::cfg_if! { + if #[cfg(all(feature = "wayland", feature = "x11"))] { + use winit::platform::unix::EventLoopWindowTargetExtUnix; + return if self.0.is_wayland() { + Platform::Wayland + } else { + Platform::X11 + }; + } else if #[cfg(feature = "wayland")] { + return Platform::Wayland; + } else if #[cfg(feature = "x11")] { + return Platform::X11; + } else { + compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); + } + } } #[cfg(target_os = "macos")] From ff84aebe73269268755da75d01f06ec4417daa8b Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 16:17:12 +0000 Subject: [PATCH 22/25] Make Clippy happy --- crates/kas-core/src/shell/common.rs | 10 ++-------- crates/kas-core/src/shell/shell.rs | 1 + crates/kas-core/src/title_bar.rs | 8 +++++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/crates/kas-core/src/shell/common.rs b/crates/kas-core/src/shell/common.rs index de3881625..87865a7b0 100644 --- a/crates/kas-core/src/shell/common.rs +++ b/crates/kas-core/src/shell/common.rs @@ -120,10 +120,7 @@ impl Platform { target_os = "netbsd", target_os = "openbsd" ))] { - match self { - Platform::Wayland => true, - _ => false, - } + matches!(self, Platform::Wayland) } else { false } @@ -162,10 +159,7 @@ impl Platform { target_os = "netbsd", target_os = "openbsd" ))] { - match self { - Platform::X11 => true, - _ => false, - } + matches!(self, Platform::X11) } else { false } diff --git a/crates/kas-core/src/shell/shell.rs b/crates/kas-core/src/shell/shell.rs index 6e6816745..ac54a083f 100644 --- a/crates/kas-core/src/shell/shell.rs +++ b/crates/kas-core/src/shell/shell.rs @@ -200,6 +200,7 @@ where pub(super) struct PlatformWrapper<'a>(&'a EventLoop); impl<'a> PlatformWrapper<'a> { /// Get platform + #[allow(clippy::needless_return)] pub(super) fn platform(&self) -> Platform { // Logic copied from winit::platform_impl module. diff --git a/crates/kas-core/src/title_bar.rs b/crates/kas-core/src/title_bar.rs index 3cfdc15a7..603cb0882 100644 --- a/crates/kas-core/src/title_bar.rs +++ b/crates/kas-core/src/title_bar.rs @@ -149,15 +149,17 @@ impl_scope! { if let Some(msg) = mgr.try_pop() { match msg { TitleBarButton::Minimize => { - mgr.winit_window().map(|w| { + if let Some(w) = mgr.winit_window() { // TODO: supported in winit 0.28: // let is_minimized = w.is_minimized().unwrap_or(false); let is_minimized = false; w.set_minimized(!is_minimized); - }); + } } TitleBarButton::Maximize => { - mgr.winit_window().map(|w| w.set_maximized(!w.is_maximized())); + if let Some(w) = mgr.winit_window() { + w.set_maximized(!w.is_maximized()); + } } TitleBarButton::Close => mgr.send_action(Action::CLOSE), } From 78678a904982a252cefb11cc0d2b4817056a6c8a Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 16:18:53 +0000 Subject: [PATCH 23/25] Fix test without winit feature --- crates/kas-core/src/event/manager/mgr_pub.rs | 1 + crates/kas-core/src/shell/common.rs | 1 + crates/kas-core/src/title_bar.rs | 2 ++ 3 files changed, 4 insertions(+) diff --git a/crates/kas-core/src/event/manager/mgr_pub.rs b/crates/kas-core/src/event/manager/mgr_pub.rs index ff4dbb809..46c8b7e86 100644 --- a/crates/kas-core/src/event/manager/mgr_pub.rs +++ b/crates/kas-core/src/event/manager/mgr_pub.rs @@ -805,6 +805,7 @@ impl<'a> EventMgr<'a> { /// Directly access Winit Window /// /// This is a temporary API, allowing e.g. to minimize the window. + #[cfg(features = "winit")] pub fn winit_window(&self) -> Option<&winit::window::Window> { self.shell.winit_window() } diff --git a/crates/kas-core/src/shell/common.rs b/crates/kas-core/src/shell/common.rs index 87865a7b0..dfb6aa8ce 100644 --- a/crates/kas-core/src/shell/common.rs +++ b/crates/kas-core/src/shell/common.rs @@ -293,6 +293,7 @@ pub(crate) trait ShellWindow { /// Directly access Winit Window /// /// This is a temporary API, allowing e.g. to minimize the window. + #[cfg(features = "winit")] fn winit_window(&self) -> Option<&winit::window::Window>; /// Access a Waker diff --git a/crates/kas-core/src/title_bar.rs b/crates/kas-core/src/title_bar.rs index 603cb0882..cf0426fd0 100644 --- a/crates/kas-core/src/title_bar.rs +++ b/crates/kas-core/src/title_bar.rs @@ -149,6 +149,7 @@ impl_scope! { if let Some(msg) = mgr.try_pop() { match msg { TitleBarButton::Minimize => { + #[cfg(features = "winit")] if let Some(w) = mgr.winit_window() { // TODO: supported in winit 0.28: // let is_minimized = w.is_minimized().unwrap_or(false); @@ -157,6 +158,7 @@ impl_scope! { } } TitleBarButton::Maximize => { + #[cfg(features = "winit")] if let Some(w) = mgr.winit_window() { w.set_maximized(!w.is_maximized()); } From 7469930c4fe12b9b01d2d513fb338e3b08d8c5c4 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Feb 2023 17:35:25 +0000 Subject: [PATCH 24/25] More fixes --- .github/workflows/test.yml | 6 +++--- crates/kas-core/src/shell/window.rs | 1 + crates/kas-core/src/theme/traits.rs | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f21050bd4..c6b1af8fb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,10 +37,10 @@ jobs: run: cargo test --manifest-path crates/kas-resvg/Cargo.toml --all-features - name: Test kas-wgpu run: | - cargo test --manifest-path crates/kas-wgpu/Cargo.toml --no-default-features --features raster - cargo test --manifest-path crates/kas-wgpu/Cargo.toml --all-features + cargo test --manifest-path crates/kas-wgpu/Cargo.toml --no-default-features --features raster,kas/x11 + cargo test --manifest-path crates/kas-wgpu/Cargo.toml --all-features --features kas/x11 - name: Test kas-dylib - run: cargo test --manifest-path crates/kas-dylib/Cargo.toml --all-features + run: cargo test --manifest-path crates/kas-dylib/Cargo.toml --all-features --features kas-core/x11 - name: Test kas run: cargo test --all-features - name: Test examples/mandlebrot diff --git a/crates/kas-core/src/shell/window.rs b/crates/kas-core/src/shell/window.rs index 13b1c2433..19e2937b2 100644 --- a/crates/kas-core/src/shell/window.rs +++ b/crates/kas-core/src/shell/window.rs @@ -512,6 +512,7 @@ where self.shared.platform } + #[cfg(features = "winit")] #[inline] fn winit_window(&self) -> Option<&winit::window::Window> { self.window diff --git a/crates/kas-core/src/theme/traits.rs b/crates/kas-core/src/theme/traits.rs index 66bf6cad2..1fd36c908 100644 --- a/crates/kas-core/src/theme/traits.rs +++ b/crates/kas-core/src/theme/traits.rs @@ -40,7 +40,7 @@ pub trait ThemeControl { /// Set colors directly /// /// This may be used to provide a custom color scheme. The `name` is - /// compulsary (and returned by [`Self::get_active_scheme`]). + /// compulsary (and returned by [`Self::active_scheme`]). /// The `name` is also used when saving config, though the custom colors are /// not currently saved in this config. fn set_colors(&mut self, name: String, scheme: ColorsLinear) -> Action; From d50a0df63ec2d56d9c37841b61f5edbe865677b3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 10 Feb 2023 09:20:26 +0000 Subject: [PATCH 25/25] More CI fixes --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6b1af8fb..24bbc4693 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -89,9 +89,9 @@ jobs: - name: Test kas-resvg run: cargo test --manifest-path crates/kas-resvg/Cargo.toml --all-features - name: Test kas-wgpu - run: cargo test --manifest-path crates/kas-wgpu/Cargo.toml + run: cargo test --manifest-path crates/kas-wgpu/Cargo.toml --features kas/x11 - name: Test kas-dylib - run: cargo test --manifest-path crates/kas-dylib/Cargo.toml + run: cargo test --manifest-path crates/kas-dylib/Cargo.toml --features kas-core/x11 - name: Test kas run: cargo test - name: Test examples/mandlebrot