diff --git a/crates/kas-core/src/app/common.rs b/crates/kas-core/src/app/common.rs index cea6448f9..8652a2586 100644 --- a/crates/kas-core/src/app/common.rs +++ b/crates/kas-core/src/app/common.rs @@ -194,6 +194,7 @@ pub trait AppGraphicsBuilder { fn new_surface<'window, W>( shared: &mut Self::Shared, window: W, + transparent: bool, ) -> Result> where W: rwh::HasWindowHandle + rwh::HasDisplayHandle + Send + Sync + 'window, diff --git a/crates/kas-core/src/app/mod.rs b/crates/kas-core/src/app/mod.rs index 0ac6f8e67..c685bf798 100644 --- a/crates/kas-core/src/app/mod.rs +++ b/crates/kas-core/src/app/mod.rs @@ -287,7 +287,11 @@ mod test { todo!() } - fn new_surface<'window, W>(_: &mut Self::Shared, _: W) -> Result> + fn new_surface<'window, W>( + _: &mut Self::Shared, + _: W, + _: bool, + ) -> Result> where W: rwh::HasWindowHandle + rwh::HasDisplayHandle + Send + Sync + 'window, Self: Sized, diff --git a/crates/kas-core/src/app/window.rs b/crates/kas-core/src/app/window.rs index 13ef64227..cd57e98b1 100644 --- a/crates/kas-core/src/app/window.rs +++ b/crates/kas-core/src/app/window.rs @@ -10,6 +10,7 @@ use super::shared::{AppSharedState, AppState}; use super::{AppData, AppGraphicsBuilder}; use crate::cast::{Cast, Conv}; use crate::config::WindowConfig; +use crate::decorations::Decorations; use crate::draw::{color::Rgba, AnimationState, DrawSharedImpl}; use crate::event::{ConfigCx, CursorIcon, EventState}; use crate::geom::{Coord, Rect, Size}; @@ -109,7 +110,7 @@ impl> Window { attrs.title = self.widget.title().to_string(); attrs.visible = false; attrs.transparent = self.widget.transparent(); - attrs.decorations = self.widget.decorations() == kas::Decorations::Server; + attrs.decorations = self.widget.decorations() == Decorations::Server; attrs.window_icon = self.widget.icon(); let (restrict_min, restrict_max) = self.widget.restrictions(); if restrict_min { @@ -173,7 +174,11 @@ impl> Window { // NOTE: usage of Arc is inelegant, but avoids lots of unsafe code let window = Arc::new(window); - let mut surface = G::new_surface(&mut state.shared.draw.draw, window.clone())?; + let mut surface = G::new_surface( + &mut state.shared.draw.draw, + window.clone(), + self.widget.transparent(), + )?; surface.do_resize(&mut state.shared.draw.draw, size); let winit_id = window.id(); diff --git a/crates/kas-core/src/decorations.rs b/crates/kas-core/src/decorations.rs index 8c218a90a..4cefc838e 100644 --- a/crates/kas-core/src/decorations.rs +++ b/crates/kas-core/src/decorations.rs @@ -3,7 +3,7 @@ // 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 +//! Window title-bar and border decorations //! //! Note: due to definition in kas-core, some widgets must be duplicated. @@ -104,7 +104,7 @@ impl_scope! { #[widget { Data = (); }] - pub struct Label { + pub(crate) struct Label { core: widget_core!(), text: Text, } @@ -166,7 +166,7 @@ impl_scope! { #[widget { hover_highlight = true; }] - pub struct MarkButton { + pub(crate) struct MarkButton { core: widget_core!(), style: MarkStyle, msg: M, @@ -215,21 +215,72 @@ enum TitleBarButton { } impl_scope! { - /// A window's title bar (part of decoration) + /// A set of title-bar buttons + /// + /// Currently, this consists of minimise, maximise and close buttons. #[derive(Clone, 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 TitleBarButtons { + core: widget_core!(), + } + + impl Self { + /// Construct + #[inline] + pub fn new() -> Self { + TitleBarButtons { + core: Default::default(), + } + } + } + + impl Events for Self { + type Data = (); + + fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) { + if let Some(msg) = cx.try_pop() { + match msg { + TitleBarButton::Minimize => { + #[cfg(winit)] + if let Some(w) = cx.winit_window() { + w.set_minimized(true); + } + } + TitleBarButton::Maximize => { + #[cfg(winit)] + if let Some(w) = cx.winit_window() { + w.set_maximized(!w.is_maximized()); + } + } + TitleBarButton::Close => cx.action(self, Action::CLOSE), + } + } + } + } +} + +impl_scope! { + /// A window's title bar (part of decoration) + #[derive(Clone, Default)] + #[widget{ + layout = row! [ + // self.icon, + self.title, + self.buttons, + ]; + }] pub struct TitleBar { core: widget_core!(), #[widget] title: Label, + #[widget] + buttons: TitleBarButtons, } impl Self { @@ -239,6 +290,7 @@ impl_scope! { TitleBar { core: Default::default(), title: Label::new(title), + buttons: Default::default(), } } @@ -265,25 +317,5 @@ impl_scope! { _ => Unused, } } - - fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) { - if let Some(msg) = cx.try_pop() { - match msg { - TitleBarButton::Minimize => { - #[cfg(winit)] - if let Some(w) = cx.winit_window() { - w.set_minimized(true); - } - } - TitleBarButton::Maximize => { - #[cfg(winit)] - if let Some(w) = cx.winit_window() { - w.set_maximized(!w.is_maximized()); - } - } - TitleBarButton::Close => cx.action(self, Action::CLOSE), - } - } - } } } diff --git a/crates/kas-core/src/draw/color.rs b/crates/kas-core/src/draw/color.rs index 022e0aa79..8a919bf85 100644 --- a/crates/kas-core/src/draw/color.rs +++ b/crates/kas-core/src/draw/color.rs @@ -264,6 +264,53 @@ impl Rgba8Srgb { ) } } + + /// Compile-time parser for sRGB and sRGBA colours + pub const fn try_parse_srgb(s: &[u8]) -> Result { + if s.len() != 6 && s.len() != 8 { + return Err(ParseError::Length); + } + + // `val` is copied from the hex crate: + // Copyright (c) 2013-2014 The Rust Project Developers. + // Copyright (c) 2015-2020 The rust-hex Developers. + const fn val(c: u8) -> Result { + match c { + b'A'..=b'F' => Ok(c - b'A' + 10), + b'a'..=b'f' => Ok(c - b'a' + 10), + b'0'..=b'9' => Ok(c - b'0'), + _ => Err(()), + } + } + + const fn byte(a: u8, b: u8) -> Result { + match (val(a), val(b)) { + (Ok(hi), Ok(lo)) => Ok(hi << 4 | lo), + _ => Err(()), + } + } + + let r = byte(s[0], s[1]); + let g = byte(s[2], s[3]); + let b = byte(s[4], s[5]); + let a = if s.len() == 8 { byte(s[6], s[7]) } else { Ok(0xFF) }; + + match (r, g, b, a) { + (Ok(r), Ok(g), Ok(b), Ok(a)) => Ok(Rgba8Srgb([r, g, b, a])), + _ => Err(ParseError::InvalidHex), + } + } + + /// Compile-time parser for sRGB and sRGBA colours + /// + /// This method has worse diagnostics on error due to limited const- + pub const fn parse_srgb(s: &[u8]) -> Rgba8Srgb { + match Self::try_parse_srgb(s) { + Ok(result) => result, + Err(ParseError::Length) => panic!("invalid length (expected 6 or 8 bytes"), + Err(ParseError::InvalidHex) => panic!("invalid hex byte (expected 0-9, a-f or A-F)"), + } + } } impl From for [u8; 4] { @@ -303,54 +350,7 @@ impl std::str::FromStr for Rgba8Srgb { if s[0] == b'#' { s = &s[1..]; } - try_parse_srgb(&s) - } -} - -/// Compile-time parser for sRGB and sRGBA colours -pub const fn try_parse_srgb(s: &[u8]) -> Result { - if s.len() != 6 && s.len() != 8 { - return Err(ParseError::Length); - } - - // `val` is copied from the hex crate: - // Copyright (c) 2013-2014 The Rust Project Developers. - // Copyright (c) 2015-2020 The rust-hex Developers. - const fn val(c: u8) -> Result { - match c { - b'A'..=b'F' => Ok(c - b'A' + 10), - b'a'..=b'f' => Ok(c - b'a' + 10), - b'0'..=b'9' => Ok(c - b'0'), - _ => Err(()), - } - } - - const fn byte(a: u8, b: u8) -> Result { - match (val(a), val(b)) { - (Ok(hi), Ok(lo)) => Ok(hi << 4 | lo), - _ => Err(()), - } - } - - let r = byte(s[0], s[1]); - let g = byte(s[2], s[3]); - let b = byte(s[4], s[5]); - let a = if s.len() == 8 { byte(s[6], s[7]) } else { Ok(0xFF) }; - - match (r, g, b, a) { - (Ok(r), Ok(g), Ok(b), Ok(a)) => Ok(Rgba8Srgb([r, g, b, a])), - _ => Err(ParseError::InvalidHex), - } -} - -/// Compile-time parser for sRGB and sRGBA colours -/// -/// This method has worse diagnostics on error due to limited const- -pub const fn parse_srgb(s: &[u8]) -> Rgba8Srgb { - match try_parse_srgb(s) { - Ok(result) => result, - Err(ParseError::Length) => panic!("invalid length (expected 6 or 8 bytes"), - Err(ParseError::InvalidHex) => panic!("invalid hex byte (expected 0-9, a-f or A-F)"), + Rgba8Srgb::try_parse_srgb(&s) } } diff --git a/crates/kas-core/src/lib.rs b/crates/kas-core/src/lib.rs index 10cfc0110..7114a7f4b 100644 --- a/crates/kas-core/src/lib.rs +++ b/crates/kas-core/src/lib.rs @@ -24,13 +24,12 @@ extern crate self as kas; // internal modules: mod action; mod core; -mod decorations; +pub mod decorations; mod popup; mod root; pub use crate::core::*; pub use action::Action; -pub use decorations::Decorations; pub use kas_macros::{autoimpl, extends, impl_default, widget}; pub use kas_macros::{cell_collection, collection, impl_anon, impl_scope, widget_index}; #[doc(inline)] pub use popup::Popup; diff --git a/crates/kas-core/src/root.rs b/crates/kas-core/src/root.rs index dfd8502f5..8be1851ad 100644 --- a/crates/kas-core/src/root.rs +++ b/crates/kas-core/src/root.rs @@ -317,7 +317,7 @@ impl Window { } /// Get the preference for window decorations - pub fn decorations(&self) -> crate::Decorations { + pub fn decorations(&self) -> Decorations { self.decorations } diff --git a/crates/kas-core/src/theme/colors.rs b/crates/kas-core/src/theme/colors.rs index 5885a6be9..067ae6ac2 100644 --- a/crates/kas-core/src/theme/colors.rs +++ b/crates/kas-core/src/theme/colors.rs @@ -5,7 +5,7 @@ //! Colour schemes -use crate::draw::color::{parse_srgb, Rgba, Rgba8Srgb}; +use crate::draw::color::{Rgba, Rgba8Srgb}; use crate::event::EventState; use crate::theme::Background; use crate::Id; @@ -226,52 +226,52 @@ impl ColorsSrgb { /// Default "light" scheme pub const LIGHT: ColorsSrgb = Colors { is_dark: false, - background: parse_srgb(b"FAFAFA"), - frame: parse_srgb(b"BCBCBC"), - accent: parse_srgb(b"8347f2"), - accent_soft: parse_srgb(b"B38DF9"), - nav_focus: parse_srgb(b"7E3FF2"), - edit_bg: parse_srgb(b"FAFAFA"), - edit_bg_disabled: parse_srgb(b"DCDCDC"), - edit_bg_error: parse_srgb(b"FFBCBC"), - text: parse_srgb(b"000000"), - text_invert: parse_srgb(b"FFFFFF"), - text_disabled: parse_srgb(b"AAAAAA"), - text_sel_bg: parse_srgb(b"A172FA"), + background: Rgba8Srgb::parse_srgb(b"FAFAFA"), + frame: Rgba8Srgb::parse_srgb(b"BCBCBC"), + accent: Rgba8Srgb::parse_srgb(b"8347f2"), + accent_soft: Rgba8Srgb::parse_srgb(b"B38DF9"), + nav_focus: Rgba8Srgb::parse_srgb(b"7E3FF2"), + edit_bg: Rgba8Srgb::parse_srgb(b"FAFAFA"), + edit_bg_disabled: Rgba8Srgb::parse_srgb(b"DCDCDC"), + edit_bg_error: Rgba8Srgb::parse_srgb(b"FFBCBC"), + text: Rgba8Srgb::parse_srgb(b"000000"), + text_invert: Rgba8Srgb::parse_srgb(b"FFFFFF"), + text_disabled: Rgba8Srgb::parse_srgb(b"AAAAAA"), + text_sel_bg: Rgba8Srgb::parse_srgb(b"A172FA"), }; /// Dark scheme pub const DARK: ColorsSrgb = Colors { is_dark: true, - background: parse_srgb(b"404040"), - frame: parse_srgb(b"AAAAAA"), - accent: parse_srgb(b"F74C00"), - accent_soft: parse_srgb(b"E77346"), - nav_focus: parse_srgb(b"D03E00"), - edit_bg: parse_srgb(b"303030"), - edit_bg_disabled: parse_srgb(b"606060"), - edit_bg_error: parse_srgb(b"a06868"), - text: parse_srgb(b"FFFFFF"), - text_invert: parse_srgb(b"000000"), - text_disabled: parse_srgb(b"CBCBCB"), - text_sel_bg: parse_srgb(b"E77346"), + background: Rgba8Srgb::parse_srgb(b"404040"), + frame: Rgba8Srgb::parse_srgb(b"AAAAAA"), + accent: Rgba8Srgb::parse_srgb(b"F74C00"), + accent_soft: Rgba8Srgb::parse_srgb(b"E77346"), + nav_focus: Rgba8Srgb::parse_srgb(b"D03E00"), + edit_bg: Rgba8Srgb::parse_srgb(b"303030"), + edit_bg_disabled: Rgba8Srgb::parse_srgb(b"606060"), + edit_bg_error: Rgba8Srgb::parse_srgb(b"a06868"), + text: Rgba8Srgb::parse_srgb(b"FFFFFF"), + text_invert: Rgba8Srgb::parse_srgb(b"000000"), + text_disabled: Rgba8Srgb::parse_srgb(b"CBCBCB"), + text_sel_bg: Rgba8Srgb::parse_srgb(b"E77346"), }; /// Blue scheme pub const BLUE: ColorsSrgb = Colors { is_dark: false, - background: parse_srgb(b"FFFFFF"), - frame: parse_srgb(b"DADADA"), - accent: parse_srgb(b"3fafd7"), - accent_soft: parse_srgb(b"7CDAFF"), - nav_focus: parse_srgb(b"3B697A"), - edit_bg: parse_srgb(b"FFFFFF"), - edit_bg_disabled: parse_srgb(b"DCDCDC"), - edit_bg_error: parse_srgb(b"FFBCBC"), - text: parse_srgb(b"000000"), - text_invert: parse_srgb(b"FFFFFF"), - text_disabled: parse_srgb(b"AAAAAA"), - text_sel_bg: parse_srgb(b"6CC0E1"), + background: Rgba8Srgb::parse_srgb(b"FFFFFF"), + frame: Rgba8Srgb::parse_srgb(b"DADADA"), + accent: Rgba8Srgb::parse_srgb(b"3fafd7"), + accent_soft: Rgba8Srgb::parse_srgb(b"7CDAFF"), + nav_focus: Rgba8Srgb::parse_srgb(b"3B697A"), + edit_bg: Rgba8Srgb::parse_srgb(b"FFFFFF"), + edit_bg_disabled: Rgba8Srgb::parse_srgb(b"DCDCDC"), + edit_bg_error: Rgba8Srgb::parse_srgb(b"FFBCBC"), + text: Rgba8Srgb::parse_srgb(b"000000"), + text_invert: Rgba8Srgb::parse_srgb(b"FFFFFF"), + text_disabled: Rgba8Srgb::parse_srgb(b"AAAAAA"), + text_sel_bg: Rgba8Srgb::parse_srgb(b"6CC0E1"), }; } diff --git a/crates/kas-wgpu/src/draw/draw_pipe.rs b/crates/kas-wgpu/src/draw/draw_pipe.rs index 2e0170d2b..d653c94a9 100644 --- a/crates/kas-wgpu/src/draw/draw_pipe.rs +++ b/crates/kas-wgpu/src/draw/draw_pipe.rs @@ -109,6 +109,7 @@ impl DrawPipe { Ok(DrawPipe { instance, + adapter, device, queue, staging_belt, diff --git a/crates/kas-wgpu/src/draw/mod.rs b/crates/kas-wgpu/src/draw/mod.rs index b73a170e8..f1f14a3a1 100644 --- a/crates/kas-wgpu/src/draw/mod.rs +++ b/crates/kas-wgpu/src/draw/mod.rs @@ -37,6 +37,7 @@ type Scale = [f32; 4]; /// Shared pipeline data pub struct DrawPipe { pub(crate) instance: wgpu::Instance, + pub(crate) adapter: wgpu::Adapter, pub(crate) device: wgpu::Device, queue: wgpu::Queue, staging_belt: wgpu::util::StagingBelt, diff --git a/crates/kas-wgpu/src/lib.rs b/crates/kas-wgpu/src/lib.rs index 292c9190a..7b340257f 100644 --- a/crates/kas-wgpu/src/lib.rs +++ b/crates/kas-wgpu/src/lib.rs @@ -61,12 +61,13 @@ impl AppGraphicsBuilder for WgpuBuilder { fn new_surface<'window, W>( shared: &mut Self::Shared, window: W, + transparent: bool, ) -> Result> where W: rwh::HasWindowHandle + rwh::HasDisplayHandle + Send + Sync + 'window, Self: Sized, { - surface::Surface::new(shared, window) + surface::Surface::new(shared, window, transparent) } } diff --git a/crates/kas-wgpu/src/options.rs b/crates/kas-wgpu/src/options.rs index abd482b28..41b49690f 100644 --- a/crates/kas-wgpu/src/options.rs +++ b/crates/kas-wgpu/src/options.rs @@ -24,7 +24,7 @@ impl Default for Options { fn default() -> Self { Options { power_preference: PowerPreference::LowPower, - backends: Backends::all(), + backends: Backends::PRIMARY, wgpu_trace_path: None, } } @@ -97,7 +97,7 @@ impl Options { "FALLBACK" => Backends::empty(), other => { log::error!("from_env: bad var KAS_BACKENDS={other}"); - log::error!("from_env: supported backends: VULKAN, GL, METAL, DX11, DX12, BROWSER_WEBGPU, PRIMARY, SECONDARY, FALLBACK"); + log::error!("from_env: supported backends: VULKAN, GL, METAL, DX12, BROWSER_WEBGPU, PRIMARY, SECONDARY, FALLBACK"); self.backends } } diff --git a/crates/kas-wgpu/src/surface.rs b/crates/kas-wgpu/src/surface.rs index aa27e8a62..997af090a 100644 --- a/crates/kas-wgpu/src/surface.rs +++ b/crates/kas-wgpu/src/surface.rs @@ -21,7 +21,11 @@ pub struct Surface<'a, C: CustomPipe> { } impl<'a, C: CustomPipe> Surface<'a, C> { - pub fn new(shared: &mut ::Shared, window: W) -> Result + pub fn new( + shared: &mut ::Shared, + window: W, + transparent: bool, + ) -> Result where W: rwh::HasWindowHandle + rwh::HasDisplayHandle + Send + Sync + 'a, Self: Sized, @@ -30,6 +34,18 @@ impl<'a, C: CustomPipe> Surface<'a, C> { .instance .create_surface(window) .map_err(|e| Error::Graphics(Box::new(e)))?; + + use wgpu::CompositeAlphaMode::{Inherit, Opaque, PostMultiplied, PreMultiplied}; + let caps = surface.get_capabilities(&shared.adapter); + let alpha_mode = match transparent { + // FIXME: data conversion is needed somewhere: + true if caps.alpha_modes.contains(&PreMultiplied) => PreMultiplied, + true if caps.alpha_modes.contains(&PostMultiplied) => PostMultiplied, + _ if caps.alpha_modes.contains(&Opaque) => Opaque, + _ => Inherit, // it is specified that either Opaque or Inherit is supported + }; + log::debug!("Surface::new: using alpha_mode={alpha_mode:?}"); + let sc_desc = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: crate::draw::RENDER_TEX_FORMAT, @@ -37,11 +53,7 @@ impl<'a, C: CustomPipe> Surface<'a, C> { height: 0, present_mode: wgpu::PresentMode::Fifo, desired_maximum_frame_latency: 2, - // 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, + alpha_mode, view_formats: vec![], }; diff --git a/examples/clock.rs b/examples/clock.rs index d4be80501..b8b96a605 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -15,7 +15,6 @@ extern crate chrono; use chrono::prelude::*; use std::f32::consts::PI; -use std::str::FromStr; use std::time::Duration; use kas::app::ApplicationInherent; @@ -70,7 +69,7 @@ impl_scope! { } fn draw(&mut self, mut draw: DrawCx) { - let accent: Rgba = Rgba8Srgb::from_str("#d7916f").unwrap().into(); + let accent: Rgba = Rgba8Srgb::parse_srgb(b"d7916f").into(); let col_back = Rgba::ga(0.0, 0.5); let col_face = accent.multiply(0.4); let col_time = Rgba::grey(1.0); @@ -173,7 +172,7 @@ fn main() -> kas::app::Result<()> { env_logger::init(); let window = Window::new(Clock::new(), "Clock") - .with_decorations(kas::Decorations::None) + .with_decorations(kas::decorations::Decorations::None) .with_transparent(true); Application::with_theme(Default::default()) diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index 1cc49af83..eb6eee34c 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -7,12 +7,13 @@ //! //! Demonstrates use of a custom draw pipe. +use kas::decorations::{Decorations, TitleBarButtons}; use kas::draw::{Draw, DrawIface, PassId}; use kas::event::{self, Command}; use kas::geom::{DVec2, Vec2, Vec3}; use kas::prelude::*; use kas::widgets::adapt::Reserve; -use kas::widgets::{format_data, format_value, Slider, Text}; +use kas::widgets::{format_data, format_value, Label, Slider, Text}; use kas_wgpu::draw::{CustomPipe, CustomPipeBuilder, CustomWindow, DrawCustom, DrawPipe}; use kas_wgpu::wgpu; use std::mem::size_of; @@ -433,15 +434,21 @@ impl_scope! { #[widget{ layout = grid! { (1, 0) => self.label, + (2, 0) => self.title, + (3, 0) => self.buttons, (0, 1) => self.iters_label.align(AlignHints::CENTER), (0, 2) => self.slider, - (1..3, 1..4) => self.mbrot, + (1..5, 1..4) => self.mbrot, }; }] struct MandlebrotUI { core: widget_core!(), #[widget(&self.mbrot)] label: Text, + #[widget] + title: Label<&'static str>, + #[widget] + buttons: TitleBarButtons, #[widget(&self.iters)] iters_label: Reserve>, #[widget(&self.iters)] @@ -457,6 +464,8 @@ impl_scope! { MandlebrotUI { core: Default::default(), label: format_data!(mbrot: &Mandlebrot, "{}", mbrot.loc()), + title: Label::new("Mandlebrot"), + buttons: Default::default(), iters_label: format_value!("{}") .with_min_size_em(3.0, 0.0), slider: Slider::up(0..=256, |_, iters| *iters) @@ -485,7 +494,8 @@ impl_scope! { fn main() -> kas::app::Result<()> { env_logger::init(); - let window = Window::new(MandlebrotUI::new(), "Mandlebrot"); + let window = + Window::new(MandlebrotUI::new(), "Mandlebrot").with_decorations(Decorations::Border); let theme = kas::theme::FlatTheme::new(); let mut app = kas::app::WgpuBuilder::new(PipeBuilder) .with_theme(theme) diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index d3fcb6a7b..522541d6d 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -7,9 +7,9 @@ use std::time::{Duration, Instant}; +use kas::decorations::Decorations; use kas::prelude::*; use kas::widgets::{format_data, row, Adapt, Button}; -use kas::Decorations; #[derive(Clone, Debug)] struct MsgReset;