Window positioning properties

Closes #165
This commit is contained in:
Jonathan Johnson 2024-09-07 09:31:40 -07:00
parent bf78da333d
commit 531e6c9ab6
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
4 changed files with 201 additions and 43 deletions

View file

@ -16,7 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
of an `Option<WindowHandle>`.
- `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

4
Cargo.lock generated
View file

@ -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",

View file

@ -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::<WidgetInstance>::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<Point<Px>>,
outer_size: &Dynamic<Size<UPx>>,
) -> 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<Point<Px>>, outer_size: &Dynamic<Size<UPx>>) {
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);
}

View file

@ -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<Px> {
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<Px> {
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<UPx>;
/// Returns the current outer size of the window.
fn outer_size(&self) -> Size<UPx> {
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<Value<bool>>,
resize_increments: Option<Value<Size<UPx>>>,
visible: Option<Dynamic<bool>>,
outer_size: Option<Dynamic<Size<UPx>>>,
inner_position: Option<Dynamic<Point<Px>>>,
outer_position: Option<Dynamic<Point<Px>>>,
close_requested: Option<Callback<(), bool>>,
}
@ -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<Size<UPx>>) -> 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<Size<UPx>>) -> 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<Point<Px>>) -> 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<Point<Px>>) -> 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<T> {
occluded: Dynamic<bool>,
focused: Dynamic<bool>,
inner_size: Tracked<Dynamic<Size<UPx>>>,
outer_size: Dynamic<Size<UPx>>,
keyboard_activated: Option<WidgetId>,
min_inner_size: Option<Size<UPx>>,
max_inner_size: Option<Size<UPx>>,
@ -967,6 +1026,8 @@ struct OpenWindow<T> {
resizable: Tracked<Value<bool>>,
resize_increments: Tracked<Value<Size<UPx>>>,
visible: Tracked<Dynamic<bool>>,
outer_position: Tracked<Dynamic<Point<Px>>>,
inner_position: Dynamic<Point<Px>>,
}
impl<T> OpenWindow<T>
@ -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<Px>, new_outer_position: Point<Px>) {
self.outer_position.set_and_read(new_outer_position);
self.inner_position.set(new_inner_position);
}
fn update_ized<W>(&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::<i32>::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<PhysicalSize<f32>> =
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<bool>,
pub resize_increments: Value<Size<UPx>>,
pub visible: Dynamic<bool>,
pub inner_position: Dynamic<Point<Px>>,
pub outer_position: Dynamic<Point<Px>>,
pub outer_size: Dynamic<Size<UPx>>,
}
#[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<Px>) {
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<Px>) {
self.cushy.set_position(new_position);
}
/// Provide keyboard input to this virtual window.
///
/// Returns whether the event was [`HANDLED`] or [`IGNORED`].