diff --git a/CHANGELOG.md b/CHANGELOG.md index 6af5ce2..dbfc60c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 of an `Option`. - `CushyWindow::set_occluded` and `CushyWindow::resize now require a `PlatformWindowImplementation` parameter. - +- `PlatformWindowImplementation::position` has been renamed to + `PlatformWindowImplementation::outer_position`. ### Fixed @@ -66,6 +67,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Window::window_level` - `Window::minimized` - `Window::maximized` + - `Window::outer_size` + - `Window::inner_position` + - `Window::outer_position` - `Window::resized` - `Window::resize_increments` - `Window::transparent` @@ -77,6 +81,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `App::monitors()` returns a snapshot of the currently configured monitors attached to the device. A new example demonstrating this API is available at `examples/monitors.rs`. +- `PlatformWindowImplementation` has several new functions with provided + implementations for winit users: + + - `inner_position` + - `outer_size` [139]: https://github.com/khonsulabs/cushy/issues/139 diff --git a/Cargo.lock b/Cargo.lock index bedd204..b446fae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,7 +124,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "appit" version = "0.3.2" -source = "git+https://github.com/khonsulabs/appit#4cab6aedd10d179ed730397dc48ca6b23af6a431" +source = "git+https://github.com/khonsulabs/appit#6c59e6942fdeecf8405f0b0ee1098aeb4bef9e60" dependencies = [ "winit", ] @@ -1309,7 +1309,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kludgine" version = "0.10.0" -source = "git+https://github.com/khonsulabs/kludgine#a468845f84cbee812778f91fe55cf02716efebd8" +source = "git+https://github.com/khonsulabs/kludgine#7d2d0dae63ba14142cb5355f10cfff074659ae27" dependencies = [ "ahash", "alot", diff --git a/examples/window-properties.rs b/examples/window-properties.rs index cf1bd75..bfdee3e 100644 --- a/examples/window-properties.rs +++ b/examples/window-properties.rs @@ -1,23 +1,79 @@ use cushy::figures::Size; -use cushy::value::{Dynamic, Source}; -use cushy::widget::{MakeWidget, WidgetInstance}; -use cushy::Run; +use cushy::value::{Destination, Dynamic, Source}; +use cushy::widget::MakeWidget; +use cushy::{run, App, Open}; +use figures::units::{Px, UPx}; +use figures::{IntoSigned, Point, Px2D, UPx2D}; fn main() -> cushy::Result { - let focused = Dynamic::new(false); - let occluded = Dynamic::new(false); - let inner_size = Dynamic::new(Size::default()); + run(|app| { + let focused = Dynamic::new(false); + let occluded = Dynamic::new(false); + let maximized = Dynamic::new(false); + let minimized = Dynamic::new(false); + let inner_size = Dynamic::new(Size::upx(0, 0)); + let outer_size = Dynamic::new(Size::upx(0, 0)); + let inner_position = Dynamic::new(Point::px(0, 0)); + let outer_position = Dynamic::new(Point::px(0, 0)); - let widgets = focused - .map_each(|v| format!("focused: {:?}", v)) - .and(occluded.map_each(|v| format!("occluded: {:?}", v))) - .and(inner_size.map_each(|v| format!("inner_size: {:?}", v))) - .into_rows() - .centered(); + let widgets = focused + .map_each(|v| format!("focused: {:?}", v)) + .and(occluded.map_each(|v| format!("occluded: {:?}", v))) + .and(maximized.map_each(|v| format!("maximized: {:?}", v))) + .and(minimized.map_each(|v| format!("minimized: {:?}", v))) + .and(inner_position.map_each(|v| format!("inner_position: {:?}", v))) + .and(outer_position.map_each(|v| format!("outer_position: {:?}", v))) + .and(inner_size.map_each(|v| format!("inner_size: {:?}", v))) + .and(outer_size.map_each(|v| format!("outer_size: {:?}", v))) + .and(center_window_button(app, &outer_position, &outer_size)) + .into_rows() + .centered(); - cushy::window::Window::::for_widget(widgets) - .focused(focused) - .occluded(occluded) - .inner_size(inner_size) - .run() + widgets + .into_window() + .focused(focused) + .occluded(occluded) + .inner_size(inner_size) + .outer_size(outer_size) + .inner_position(inner_position) + .outer_position(outer_position) + .maximized(maximized) + .minimized(minimized) + .open(app) + .expect("app running"); + }) +} + +fn center_window_button( + app: &App, + position: &Dynamic>, + outer_size: &Dynamic>, +) -> impl MakeWidget { + "Center window".into_button().on_click({ + let app = app.clone(); + let outer_size = outer_size.clone(); + let position = position.clone(); + move |_| { + center_window(&app, &position, &outer_size); + } + }) +} + +fn center_window(app: &App, position: &Dynamic>, outer_size: &Dynamic>) { + let Some(monitors) = app.monitors() else { + return; + }; + let Some(monitor) = monitors + .available + .iter() + .find(|m| m.region().contains(position.get())) + .or(monitors.primary.as_ref()) + .or(monitors.available.first()) + else { + return; + }; + let region = monitor.region(); + let window_size = outer_size.get().into_signed(); + let empty_space = region.size - window_size; + position.set(region.origin + empty_space / 2); } diff --git a/src/window.rs b/src/window.rs index 147dc28..e935dd3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -87,8 +87,25 @@ pub trait PlatformWindowImplementation { fn set_cursor(&mut self, cursor: Cursor); /// Returns a handle for the window. fn handle(&self, redraw_status: InvalidationStatus) -> WindowHandle; + /// Returns the current outer position of the window. + fn outer_position(&self) -> Point { + self.winit().map_or_else(Point::default, |w| { + w.outer_position().unwrap_or_default().into() + }) + } + /// Returns the current inner position of the window. + fn inner_position(&self) -> Point { + self.winit().map_or_else(Point::default, |w| { + w.inner_position().unwrap_or_default().into() + }) + } /// Returns the current inner size of the window. fn inner_size(&self) -> Size; + /// Returns the current outer size of the window. + fn outer_size(&self) -> Size { + self.winit() + .map_or_else(|| self.inner_size(), |w| w.outer_size().into()) + } /// Returns true if the window can have its size changed. /// @@ -527,6 +544,9 @@ where resizable: Option>, resize_increments: Option>>, visible: Option>, + outer_size: Option>>, + inner_position: Option>>, + outer_position: Option>>, close_requested: Option>, } @@ -615,6 +635,9 @@ where resizable: None, resize_increments: None, visible: None, + outer_size: None, + inner_position: None, + outer_position: None, } } @@ -655,8 +678,40 @@ where /// the dynamic is updated with a new value, a resize request will be made /// with the new inner size. pub fn inner_size(mut self, inner_size: impl IntoDynamic>) -> Self { - let inner_size = inner_size.into_dynamic(); - self.inner_size = Some(inner_size); + self.inner_size = Some(inner_size.into_dynamic()); + self + } + + /// Sets `outer_size` to be a dynamic synchronized with this window's size, + /// including decorations. + /// + /// When the window is resized, the dynamic will contain its new size. + /// Setting this dynamic with a new value does not change the window in any + /// way. To resize the window, use [`inner_size`](Self::inner_size). + pub fn outer_size(mut self, outer_size: impl IntoDynamic>) -> Self { + self.outer_size = Some(outer_size.into_dynamic()); + self + } + + /// Sets `position` to be a dynamic synchronized with this window's outer + /// position. + /// + /// When the window is moved, this dynamic will contain its new position. + /// Setting this dynamic will attempt to move the window to the provided + /// location. + pub fn outer_position(mut self, position: impl IntoDynamic>) -> Self { + self.outer_position = Some(position.into_dynamic()); + self + } + + /// Sets `position` to be a dynamic synchronized with this window's inner + /// position. + /// + /// When the window is moved, this dynamic will contain its new position. + /// Setting this dynamic to a new value has no effect. To move a window, use + /// [`outer_position`](Self::outer_position). + pub fn inner_position(mut self, position: impl IntoDynamic>) -> Self { + self.inner_position = Some(position.into_dynamic()); self } @@ -874,6 +929,9 @@ where resizable: self.resizable.unwrap_or_else(|| Value::Constant(true)), resize_increments: self.resize_increments.unwrap_or_default(), visible: self.visible.unwrap_or_default(), + inner_position: self.inner_position.unwrap_or_default(), + outer_position: self.outer_position.unwrap_or_default(), + outer_size: self.outer_size.unwrap_or_default(), }), pending: self.pending, }, @@ -941,6 +999,7 @@ struct OpenWindow { occluded: Dynamic, focused: Dynamic, inner_size: Tracked>>, + outer_size: Dynamic>, keyboard_activated: Option, min_inner_size: Option>, max_inner_size: Option>, @@ -967,6 +1026,8 @@ struct OpenWindow { resizable: Tracked>, resize_increments: Tracked>>, visible: Tracked>, + outer_position: Tracked>>, + inner_position: Dynamic>, } impl OpenWindow @@ -1329,9 +1390,11 @@ where cushy.fonts.clone(), graphics.font_system().db_mut(), ); - let inner_size = settings.inner_size; - inner_size.set(window.inner_size()); + settings.inner_position.set(window.inner_position()); + settings.outer_position.set(window.outer_position()); + settings.inner_size.set(window.inner_size()); + settings.outer_size.set(window.outer_size()); let dpi_scale = Dynamic::new(graphics.dpi_scale()); @@ -1368,7 +1431,7 @@ where initial_frame: true, occluded: settings.occluded, focused: settings.focused, - inner_size: Tracked::from(inner_size), + inner_size: Tracked::from(settings.inner_size), keyboard_activated: None, min_inner_size: None, max_inner_size: None, @@ -1395,6 +1458,9 @@ where resizable: Tracked::from(settings.resizable), resize_increments: Tracked::from(settings.resize_increments), visible: Tracked::from(settings.visible), + outer_size: settings.outer_size, + inner_position: settings.inner_position, + outer_position: Tracked::from(settings.outer_position), } } @@ -1548,13 +1614,17 @@ where where W: PlatformWindowImplementation, { - self.inner_size.set(new_size); - // We want to prevent a resize request for this resized event. - self.inner_size.mark_read(); + self.inner_size.set_and_read(new_size); + self.outer_size.set(window.outer_size()); self.update_ized(window); self.root.invalidate(); } + fn moved(&mut self, new_inner_position: Point, new_outer_position: Point) { + self.outer_position.set_and_read(new_outer_position); + self.inner_position.set(new_inner_position); + } + fn update_ized(&mut self, window: &W) where W: PlatformWindowImplementation, @@ -1576,33 +1646,34 @@ where { self.update_ized(window); if let Some(winit) = window.winit() { + let handle = window.handle(self.redraw_status.clone()); + self.outer_position.inner_sync_when_changed(handle.clone()); + if let Some(position) = self.outer_position.updated() { + winit.set_outer_position(PhysicalPosition::::from(*position)); + } self.content_protected - .inner_sync_when_changed(window.handle(self.redraw_status.clone())); + .inner_sync_when_changed(handle.clone()); if let Some(protected) = self.content_protected.updated() { winit.set_content_protected(*protected); } - self.cursor_hittest - .inner_sync_when_changed(window.handle(self.redraw_status.clone())); + self.cursor_hittest.inner_sync_when_changed(handle.clone()); if let Some(hit) = self.cursor_hittest.updated() { let _ = winit.set_cursor_hittest(*hit); } - self.cursor_visible - .inner_sync_when_changed(window.handle(self.redraw_status.clone())); + self.cursor_visible.inner_sync_when_changed(handle.clone()); if let Some(visible) = self.cursor_visible.updated() { winit.set_cursor_visible(*visible); } - self.window_level - .inner_sync_when_changed(window.handle(self.redraw_status.clone())); + self.window_level.inner_sync_when_changed(handle.clone()); if let Some(window_level) = self.window_level.updated() { winit.set_window_level(*window_level); } - self.decorated - .inner_sync_when_changed(window.handle(self.redraw_status.clone())); + self.decorated.inner_sync_when_changed(handle.clone()); if let Some(decorated) = self.decorated.updated() { winit.set_decorations(*decorated); } self.resize_increments - .inner_sync_when_changed(window.handle(self.redraw_status.clone())); + .inner_sync_when_changed(handle.clone()); if let Some(resize_increments) = self.resize_increments.updated() { let increments: Option> = if resize_increments.width > 0 || resize_increments.height > 0 { @@ -1615,13 +1686,11 @@ where }; winit.set_resize_increments(increments); } - self.visible - .inner_sync_when_changed(window.handle(self.redraw_status.clone())); + self.visible.inner_sync_when_changed(handle.clone()); if let Some(visible) = self.visible.updated() { winit.set_visible(*visible); } - self.resizable - .inner_sync_when_changed(window.handle(self.redraw_status.clone())); + self.resizable.inner_sync_when_changed(handle.clone()); if let Some(resizable) = self.resizable.updated() { winit.set_resizable(*resizable); window.set_needs_redraw(); @@ -2076,7 +2145,7 @@ where window: kludgine::app::Window<'_, WindowCommand>, _kludgine: &mut Kludgine, ) { - self.set_occluded(&window, window.ocluded()); + self.set_occluded(&window, window.occluded()); } fn render<'pass>( @@ -2173,6 +2242,14 @@ where self.resized(window.inner_size(), &window); } + fn moved( + &mut self, + window: kludgine::app::Window<'_, WindowCommand>, + _kludgine: &mut Kludgine, + ) { + self.moved(window.inner_position(), window.outer_position()); + } + // fn theme_changed(&mut self, window: kludgine::app::Window<'_, ()>) {} // fn dropped_file(&mut self, window: kludgine::app::Window<'_, ()>, path: std::path::PathBuf) {} @@ -2449,6 +2526,9 @@ pub(crate) mod sealed { pub resizable: Value, pub resize_increments: Value>, pub visible: Dynamic, + pub inner_position: Dynamic>, + pub outer_position: Dynamic>, + pub outer_size: Dynamic>, } #[derive(Debug, Clone)] @@ -3056,6 +3136,9 @@ impl StandaloneWindowBuilder { resizable: Value::Constant(true), resize_increments: Value::default(), visible: Dynamic::new(true), + inner_position: Dynamic::default(), + outer_position: Dynamic::default(), + outer_size: Dynamic::default(), }, ); @@ -3219,6 +3302,11 @@ impl CushyWindow { self.window.resized(new_size, window); } + /// Sets the window's position. + pub fn set_position(&mut self, new_position: Point) { + self.window.moved(new_position, new_position); + } + /// Provide keyboard input to this virtual window. /// /// Returns whether the event was [`HANDLED`] or [`IGNORED`]. @@ -3443,6 +3531,11 @@ impl VirtualWindow { ); } + /// Sets the window's position. + pub fn set_position(&mut self, new_position: Point) { + self.cushy.set_position(new_position); + } + /// Provide keyboard input to this virtual window. /// /// Returns whether the event was [`HANDLED`] or [`IGNORED`].