diff --git a/crates/kas-core/Cargo.toml b/crates/kas-core/Cargo.toml index eefb6e725..b2f646959 100644 --- a/crates/kas-core/Cargo.toml +++ b/crates/kas-core/Cargo.toml @@ -71,8 +71,8 @@ linear-map = "1.2.0" thiserror = "1.0.23" serde = { version = "1.0.123", features = ["derive"], optional = true } serde_json = { version = "1.0.61", optional = true } -serde_yaml = { version = "0.8.16", optional = true } -ron = { version = "0.7.1", package = "ron", optional = true } +serde_yaml = { version = "0.9.9", optional = true } +ron = { version = "0.8.0", package = "ron", optional = true } num_enum = "0.5.6" [dependencies.kas-macros] @@ -81,15 +81,11 @@ path = "../kas-macros" [dependencies.kas-text] version = "0.5.0" -git = "https://github.com/kas-gui/kas-text.git" -rev = "e6278e05761c306b9a3527331445d76745a1a1ca" [dependencies.easy-cast] -git = "https://github.com/kas-gui/easy-cast.git" -rev = "6bf6084bb78f6bd1e781158016916ef103db0b19" version = "0.5.0" # used in doc links [dependencies.winit] # Provides translations for several winit types -version = "0.26" +version = "0.27" optional = true diff --git a/crates/kas-core/src/config.rs b/crates/kas-core/src/config.rs index 4d30570fc..9d379e57a 100644 --- a/crates/kas-core/src/config.rs +++ b/crates/kas-core/src/config.rs @@ -25,9 +25,14 @@ pub enum Error { #[cfg(feature = "ron")] #[cfg_attr(doc_cfg, doc(cfg(feature = "ron")))] - #[error("config (de)serialisation to RON failed")] + #[error("config serialisation to RON failed")] Ron(#[from] ron::Error), + #[cfg(feature = "ron")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "ron")))] + #[error("config deserialisation from RON failed")] + RonSpanned(#[from] ron::error::SpannedError), + #[error("error reading / writing config file")] IoError(#[from] std::io::Error), diff --git a/crates/kas-core/src/event/config.rs b/crates/kas-core/src/event/config.rs index 8f599c7e1..f75491e38 100644 --- a/crates/kas-core/src/event/config.rs +++ b/crates/kas-core/src/event/config.rs @@ -184,7 +184,7 @@ impl WindowConfig { /// /// Calculates scroll distance from `(horiz, vert)` lines. pub fn scroll_distance(&self, lines: (f32, f32)) -> Offset { - let x = (self.scroll_dist * -lines.0).cast_nearest(); + let x = (self.scroll_dist * lines.0).cast_nearest(); let y = (self.scroll_dist * lines.1).cast_nearest(); Offset(x, y) } diff --git a/crates/kas-core/src/layout/grid_solver.rs b/crates/kas-core/src/layout/grid_solver.rs index 63fcd103f..91c30e96d 100644 --- a/crates/kas-core/src/layout/grid_solver.rs +++ b/crates/kas-core/src/layout/grid_solver.rs @@ -198,46 +198,12 @@ where .distribute_stretch_over_by(&mut widths[range.clone()], &scores[range]); } - // We merge all overlapping spans in arbitrary order. - let (mut i, mut j) = (0, 1); - let mut len = spans.len(); - while j < len { - let (first, second) = if spans[i].1 <= spans[j].1 { - (i, j) - } else { - (j, i) - }; - let first_end = usize::conv(spans[first].2); - let second_begin = usize::conv(spans[second].1); - if first_end <= second_begin { - j += 1; - if j >= len { - i += 1; - j = i + 1; - } - continue; - } - - // Internal margins would be lost; handle those first. - widths[second_begin].include_margins((spans[second].0.margins().0, 0)); - widths[first_end - 1].include_margins((0, spans[first].0.margins().1)); - - let overlap_sum = widths[second_begin..first_end].iter().sum(); - spans[first].0.sub_add(overlap_sum, spans[second].0); - debug_assert!(spans[first].1 <= spans[second].1); - spans[first].2 = spans[first].2.max(spans[second].2); - - spans.swap(second, len - 1); - len -= 1; - if j >= len { - i += 1; - j = i + 1; - } - } + // Sort spans to apply smallest first + spans.sort_by_key(|span| span.2.saturating_sub(span.1)); // We are left with non-overlapping spans. // For each span, we ensure cell widths are sufficiently large. - for span in &spans[..len] { + for span in spans { let rules = span.0; let begin = usize::conv(span.1); let end = usize::conv(span.2); diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 88b7c7e37..62f4165b6 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -256,17 +256,12 @@ pub trait AutoLayout { /// Set size and position /// - /// The implementation does not assign to `self.core.rect`; - /// [`Layout::set_rect`] should do so before calling this method. + /// This functions identically to [`Layout::set_rect`]. fn set_rect(&mut self, mgr: &mut ConfigMgr, rect: Rect); /// Translate a coordinate to a [`WidgetId`] /// - /// This method does not test `coord` against `self.rect()` nor does it ever - /// return `self.id()`; it only ever returns a child's [`WidgetId`] or - /// `None`. [`Layout::find_id`] should test against `self.rect()` before - /// calling this method, then return `self.id()` if the result of this - /// method is `None`. + /// This functions identically to [`Layout::find_id`]. fn find_id(&mut self, coord: Coord) -> Option; /// Draw a widget and its children diff --git a/crates/kas-macros/Cargo.toml b/crates/kas-macros/Cargo.toml index 3aee3b138..4859a0dd3 100644 --- a/crates/kas-macros/Cargo.toml +++ b/crates/kas-macros/Cargo.toml @@ -26,9 +26,7 @@ proc-macro-error = "1.0" bitflags = "1.3.1" [dependencies.impl-tools-lib] -version = "0.3.0" # version used in doc links -git = "https://github.com/kas-gui/impl-tools.git" -rev = "fc33ba95d15a56ff376eabcd3c2a852097171914" +version = "0.4.0" # version used in doc links [dependencies.syn] version = "1.0.14" diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 4e44d2d08..18ea5ece8 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -33,7 +33,7 @@ mod widget_index; /// Implement `Default` /// -/// See [`impl_tools::impl_default`](https://docs.rs/impl-tools/0.3/impl_tools/attr.impl_default.html) +/// See [`impl_tools::impl_default`](https://docs.rs/impl-tools/0.4/impl_tools/attr.impl_default.html) /// for full documentation. #[proc_macro_attribute] #[proc_macro_error] @@ -52,7 +52,7 @@ pub fn impl_default(attr: TokenStream, item: TokenStream) -> TokenStream { /// A variant of the standard `derive` macro /// -/// See [`impl_tools::autoimpl`](https://docs.rs/impl-tools/0.3/impl_tools/attr.autoimpl.html) +/// See [`impl_tools::autoimpl`](https://docs.rs/impl-tools/0.4/impl_tools/attr.autoimpl.html) /// for full documentation. /// /// The following traits are supported: @@ -117,7 +117,7 @@ const IMPL_SCOPE_RULES: [&'static dyn ScopeAttr; 2] = [&AttrImplDefault, &widget /// Implementation scope /// -/// See [`impl_tools::impl_scope`](https://docs.rs/impl-tools/0.3/impl_tools/macro.impl_scope.html) +/// See [`impl_tools::impl_scope`](https://docs.rs/impl-tools/0.4/impl_tools/macro.impl_scope.html) /// for full documentation. #[proc_macro_error] #[proc_macro] diff --git a/crates/kas-theme/src/dim.rs b/crates/kas-theme/src/dim.rs index 80536dbe2..96bf4ec57 100644 --- a/crates/kas-theme/src/dim.rs +++ b/crates/kas-theme/src/dim.rs @@ -352,11 +352,7 @@ impl ThemeSize for Window { // NOTE: using different variable-width stretch policies here can // cause problems (e.g. edit boxes greedily consuming too much // space). This is a hard layout problem; for now don't do this. - if bound <= limit { - SizeRules::new(bound.min(min), bound, margins, Stretch::Filler) - } else { - SizeRules::new(min, limit, margins, Stretch::Low) - } + SizeRules::new(bound.min(min), bound.min(limit), margins, Stretch::Filler) } else { let bound: i32 = text .measure_width(f32::INFINITY) @@ -366,8 +362,6 @@ impl ThemeSize for Window { } } else { let bound: i32 = text.measure_height().expect("invalid font_id").cast_ceil(); - // Reset env since measure_height adjusts vertical alignment: - text.set_env(env); let line_height = self.dims.dpem.cast_ceil(); let min = bound.max(line_height); diff --git a/crates/kas-wgpu/Cargo.toml b/crates/kas-wgpu/Cargo.toml index f35ea6049..9003674e5 100644 --- a/crates/kas-wgpu/Cargo.toml +++ b/crates/kas-wgpu/Cargo.toml @@ -32,7 +32,7 @@ futures = "0.3" log = "0.4" smallvec = "1.6.1" wgpu = { version = "0.13.0", features = ["spirv"] } -winit = "0.26" +winit = "0.27" thiserror = "1.0.23" window_clipboard = { version = "0.2.0", optional = true } guillotiere = "0.6.0" @@ -53,8 +53,6 @@ default-features = false [dependencies.kas-text] version = "0.5.0" -git = "https://github.com/kas-gui/kas-text.git" -rev = "e6278e05761c306b9a3527331445d76745a1a1ca" [build-dependencies] glob = "0.3" diff --git a/crates/kas-wgpu/src/event_loop.rs b/crates/kas-wgpu/src/event_loop.rs index a6b73a57d..535632fd2 100644 --- a/crates/kas-wgpu/src/event_loop.rs +++ b/crates/kas-wgpu/src/event_loop.rs @@ -61,33 +61,6 @@ where use Event::*; match event { - WindowEvent { window_id, event } => { - if let Some(window) = self.windows.get_mut(&window_id) { - window.handle_event(&mut self.shared, event); - } - } - - DeviceEvent { .. } => return, // windows handle local input; we do not handle global input - UserEvent(action) => match action { - ProxyAction::Close(id) => { - if let Some(id) = self.id_map.get(&id) { - if let Some(window) = self.windows.get_mut(id) { - window.send_action(TkAction::CLOSE); - } - } - } - ProxyAction::CloseAll => { - for window in self.windows.values_mut() { - window.send_action(TkAction::CLOSE); - } - } - ProxyAction::Update(handle, payload) => { - self.shared - .pending - .push(PendingAction::Update(handle, payload)); - } - }, - NewEvents(cause) => { // MainEventsCleared will reset control_flow (but not when it is Poll) *control_flow = ControlFlow::Wait; @@ -122,13 +95,97 @@ where // log::debug!("Wakeup: WaitCancelled (ignoring)"); } StartCause::Poll => (), - StartCause::Init => { - log::debug!("Wakeup: init"); - } + StartCause::Init => (), } } + WindowEvent { window_id, event } => { + if let Some(window) = self.windows.get_mut(&window_id) { + window.handle_event(&mut self.shared, event); + } + } + DeviceEvent { .. } => { + // windows handle local input; we do not handle global input + } + UserEvent(action) => match action { + ProxyAction::Close(id) => { + if let Some(id) = self.id_map.get(&id) { + if let Some(window) = self.windows.get_mut(id) { + window.send_action(TkAction::CLOSE); + } + } + } + ProxyAction::CloseAll => { + for window in self.windows.values_mut() { + window.send_action(TkAction::CLOSE); + } + } + ProxyAction::Update(handle, payload) => { + self.shared + .pending + .push(PendingAction::Update(handle, payload)); + } + }, + + // TODO: windows should be constructed in Resumed and destroyed + // (everything but the widget) in Suspended: + Suspended => (), + Resumed => (), + MainEventsCleared => { + while let Some(pending) = self.shared.pending.pop() { + match pending { + PendingAction::AddPopup(parent_id, id, popup) => { + log::debug!("Pending: adding overlay"); + // TODO: support pop-ups as a special window, where available + self.windows.get_mut(&parent_id).unwrap().add_popup( + &mut self.shared, + id, + popup, + ); + self.id_map.insert(id, parent_id); + } + PendingAction::AddWindow(id, widget) => { + log::debug!("Pending: adding window {}", widget.title()); + match Window::new(&mut self.shared, elwt, id, widget) { + Ok(window) => { + let wid = window.window.id(); + self.id_map.insert(id, wid); + self.windows.insert(wid, window); + } + Err(e) => { + log::error!("Unable to create window: {}", e); + } + }; + } + PendingAction::CloseWindow(id) => { + if let Some(wwid) = self.id_map.get(&id) { + if let Some(window) = self.windows.get_mut(wwid) { + window.send_close(&mut self.shared, id); + } + self.id_map.remove(&id); + } + } + PendingAction::TkAction(action) => { + if action.contains(TkAction::CLOSE | TkAction::EXIT) { + for (_, window) in self.windows.drain() { + let _ = window.handle_closure(&mut self.shared); + } + *control_flow = ControlFlow::Poll; + } else { + for (_, window) in self.windows.iter_mut() { + window.handle_action(&mut self.shared, action); + } + } + } + PendingAction::Update(handle, payload) => { + for window in self.windows.values_mut() { + window.update_widgets(&mut self.shared, handle, payload); + } + } + } + } + let mut close_all = false; let mut to_close = SmallVec::<[ww::WindowId; 4]>::new(); self.resumes.clear(); @@ -165,9 +222,11 @@ where self.resumes.sort_by_key(|item| item.0); - *control_flow = if *control_flow == ControlFlow::Exit || self.windows.is_empty() { + let is_exit = matches!(control_flow, ControlFlow::ExitWithCode(_)); + *control_flow = if is_exit || self.windows.is_empty() { self.shared.on_exit(); - ControlFlow::Exit + debug_assert!(!is_exit || matches!(control_flow, ControlFlow::ExitWithCode(0))); + ControlFlow::ExitWithCode(0) } else if *control_flow == ControlFlow::Poll { ControlFlow::Poll } else if let Some((instant, _)) = self.resumes.first() { @@ -184,7 +243,6 @@ where } } } - RedrawEventsCleared => { if matches!(control_flow, ControlFlow::Wait | ControlFlow::WaitUntil(_)) { self.resumes.clear(); @@ -202,61 +260,7 @@ where } } - LoopDestroyed | Suspended | Resumed => return, - }; - - // Create and init() any new windows. - while let Some(pending) = self.shared.pending.pop() { - match pending { - PendingAction::AddPopup(parent_id, id, popup) => { - log::debug!("Pending: adding overlay"); - // TODO: support pop-ups as a special window, where available - self.windows.get_mut(&parent_id).unwrap().add_popup( - &mut self.shared, - id, - popup, - ); - self.id_map.insert(id, parent_id); - } - PendingAction::AddWindow(id, widget) => { - log::debug!("Pending: adding window {}", widget.title()); - match Window::new(&mut self.shared, elwt, id, widget) { - Ok(window) => { - let wid = window.window.id(); - self.id_map.insert(id, wid); - self.windows.insert(wid, window); - } - Err(e) => { - log::error!("Unable to create window: {}", e); - } - }; - } - PendingAction::CloseWindow(id) => { - if let Some(wwid) = self.id_map.get(&id) { - if let Some(window) = self.windows.get_mut(wwid) { - window.send_close(&mut self.shared, id); - } - self.id_map.remove(&id); - } - } - PendingAction::TkAction(action) => { - if action.contains(TkAction::CLOSE | TkAction::EXIT) { - for (_, window) in self.windows.drain() { - let _ = window.handle_closure(&mut self.shared); - } - *control_flow = ControlFlow::Poll; - } else { - for (_, window) in self.windows.iter_mut() { - window.handle_action(&mut self.shared, action); - } - } - } - PendingAction::Update(handle, payload) => { - for window in self.windows.values_mut() { - window.update_widgets(&mut self.shared, handle, payload); - } - } - } + LoopDestroyed => (), } } } diff --git a/crates/kas-wgpu/src/lib.rs b/crates/kas-wgpu/src/lib.rs index 85c3ee571..bf4f52e14 100644 --- a/crates/kas-wgpu/src/lib.rs +++ b/crates/kas-wgpu/src/lib.rs @@ -38,7 +38,7 @@ use kas::WindowId; use kas_theme::Theme; use thiserror::Error; use winit::error::OsError; -use winit::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; +use winit::event_loop::{EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget}; use crate::draw::{CustomPipe, CustomPipeBuilder, DrawPipe}; use crate::shared::SharedState; @@ -141,7 +141,7 @@ where mut theme: T, options: Options, ) -> Result { - let el = EventLoop::with_user_event(); + let el = EventLoopBuilder::with_user_event().build(); options.init_theme_config(&mut theme)?; let config = match options.read_config() { @@ -174,7 +174,7 @@ where options: Options, config: SharedRc, ) -> Result { - let el = EventLoop::with_user_event(); + let el = EventLoopBuilder::with_user_event().build(); let scale_factor = find_scale_factor(&el); Ok(Toolkit { el, diff --git a/examples/layout.rs b/examples/layout.rs index 00eeae011..6219d5353 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -6,7 +6,7 @@ //! Demonstration of widget and text layouts use kas::macros::impl_singleton; -use kas::widgets::{CheckBox, EditBox, Label, ScrollLabel}; +use kas::widgets::{CheckBox, EditBox, ScrollLabel}; const LIPSUM: &'static str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nunc mi, consequat eget urna ut, auctor luctus mi. Sed molestie mi est. Sed non ligula ante. Curabitur ac molestie ante, nec sodales eros. In non arcu at turpis euismod bibendum ut tincidunt eros. Suspendisse blandit maximus nisi, viverra hendrerit elit efficitur et. Morbi ut facilisis eros. Vivamus dignissim, sapien sed mattis consectetur, libero leo imperdiet turpis, ac pulvinar libero purus eu lorem. Etiam quis sollicitudin urna. Integer vitae erat vel neque gravida blandit ac non quam."; const CRASIT: &'static str = "Cras sit amet justo ipsum. Aliquam in nunc posuere leo egestas laoreet convallis eu libero. Nullam ut massa ante. Cras vitae velit pharetra, euismod nisl suscipit, feugiat nulla. Aenean consectetur, diam non tristique iaculis, nisl lectus hendrerit sapien, nec rhoncus mi sem non odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nulla a lorem eu ipsum faucibus placerat ac quis quam. Curabitur justo ligula, laoreet nec ultrices eu, scelerisque non metus. Mauris sit amet est enim. Mauris risus eros, accumsan ut iaculis sit amet, sagittis facilisis neque. Nunc venenatis risus nec purus malesuada, a tristique arcu efficitur. Nulla suscipit arcu nibh. Cras facilisis nibh a gravida aliquet. Praesent fringilla felis a tristique luctus."; @@ -19,7 +19,7 @@ fn main() -> kas::shell::Result<()> { layout = grid: { 1, 0: "Layout demo"; 2, 0: self.check; - 0..3, 1: Label::new(LIPSUM); + 0..3, 1: ScrollLabel::new(LIPSUM); 0, 2: align(center): "abc אבג def"; 1..3, 3: align(stretch): ScrollLabel::new(CRASIT); 0, 3: self.edit;