MakeWindow + easy window centering

This commit is contained in:
Jonathan Johnson 2024-09-08 13:19:55 -07:00
parent dd4c544ba6
commit 14d2069fec
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
5 changed files with 209 additions and 94 deletions

View file

@ -23,6 +23,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
initial value of the dynamic or whether to let the operating system perform initial value of the dynamic or whether to let the operating system perform
the initial positioning. the initial positioning.
### Changed
- `Open` is now implemented for most types via a blanket implementation for a
new trait, `MakeWindow`. `MakeWindow` splits the process of creating a
`Window<Behavior>` from the process of opening a window.
The new `MakeWindow` trait adds some new functionality:
`open_centered`/`run_centered`/`run_centered_in`. These functions present a
window centered on the screen where the window initially is shown.
### Fixed ### Fixed
- `Collapse`, `OverlayLayer`, and `Progress` all honor the theme components - `Collapse`, `OverlayLayer`, and `Progress` all honor the theme components

View file

@ -7,8 +7,7 @@ use alot::OrderedLots;
use crate::value::{Dynamic, DynamicReader, ForEach, Source, WeakDynamic}; use crate::value::{Dynamic, DynamicReader, ForEach, Source, WeakDynamic};
use crate::widget::{MakeWidget, WidgetInstance, WidgetList}; use crate::widget::{MakeWidget, WidgetInstance, WidgetList};
use crate::widgets::grid::{Grid, GridWidgets}; use crate::widgets::grid::{Grid, GridWidgets};
use crate::window::Window; use crate::window::{MakeWindow, Window};
use crate::{Application, Open, PendingApp};
/// A widget that can provide extra information when debugging. /// A widget that can provide extra information when debugging.
#[derive(Clone, Default)] #[derive(Clone, Default)]
@ -96,14 +95,6 @@ impl DebugContext {
Self { section } Self { section }
} }
fn into_window(self) -> Window {
self.section
.map_ref(|section| section.widget.clone())
// .vertical_scroll()
.into_window()
.titled("Cushy Debugger")
}
/// Returns true if this debug context has no child sections or observed /// Returns true if this debug context has no child sections or observed
/// values. /// values.
#[must_use] #[must_use]
@ -114,16 +105,15 @@ impl DebugContext {
} }
} }
impl Open for DebugContext { impl MakeWindow for DebugContext {
fn open<App>(self, app: &mut App) -> crate::Result<crate::window::WindowHandle> type Behavior = WidgetInstance;
where
App: Application + ?Sized,
{
self.into_window().open(app)
}
fn run_in(self, app: PendingApp) -> crate::Result { fn make_window(self) -> Window<Self::Behavior> {
self.into_window().run_in(app) self.section
.map_ref(|section| section.widget.clone())
// .vertical_scroll()
.make_window()
.titled("Cushy Debugger")
} }
} }

View file

@ -550,7 +550,6 @@ pub trait Destination<T> {
/// ///
/// This function panics if this value is already locked by the current /// This function panics if this value is already locked by the current
/// thread. /// thread.
#[must_use]
fn take(&self) -> T fn take(&self) -> T
where where
Self: Source<T>, Self: Source<T>,
@ -1919,17 +1918,29 @@ where
} }
} }
#[derive(Default)]
struct ChangeCallbacksExecutor {
thread: Option<ThreadId>,
callbacks_to_remove: Vec<LotId>,
}
#[derive(Default)] #[derive(Default)]
struct ChangeCallbacksData { struct ChangeCallbacksData {
callbacks: Mutex<CallbacksList>, callbacks: Mutex<CallbacksList>,
currently_executing: Mutex<Option<ThreadId>>, currently_executing: Mutex<ChangeCallbacksExecutor>,
sync: Condvar, sync: Condvar,
} }
impl CallbackCollection for ChangeCallbacksData { impl CallbackCollection for ChangeCallbacksData {
fn remove(&self, id: LotId) { fn remove(&self, id: LotId) {
let mut data = self.callbacks.lock(); let mut currently_executing = self.currently_executing.lock();
data.callbacks.remove(id); if currently_executing.thread == Some(thread::current().id()) {
currently_executing.callbacks_to_remove.push(id);
} else {
drop(currently_executing);
let mut data = self.callbacks.lock();
data.callbacks.remove(id);
}
} }
} }
@ -1957,12 +1968,12 @@ impl Drop for ChangeCallbacks {
let mut currently_executing = self.data.currently_executing.lock(); let mut currently_executing = self.data.currently_executing.lock();
let current_thread = thread::current().id(); let current_thread = thread::current().id();
loop { loop {
match &*currently_executing { match &currently_executing.thread {
None => { None => {
// No other thread is executing these callbacks. Set this // No other thread is executing these callbacks. Set this
// thread as the current executor so that we can prevent // thread as the current executor so that we can prevent
// infinite cycles. // infinite cycles.
*currently_executing = Some(current_thread); currently_executing.thread = Some(current_thread);
drop(currently_executing); drop(currently_executing);
// Invoke the callbacks // Invoke the callbacks
@ -1978,12 +1989,15 @@ impl Drop for ChangeCallbacks {
.callbacks .callbacks
.drain_filter(|callback| callback.changed().is_err()); .drain_filter(|callback| callback.changed().is_err());
} }
drop(state);
// Remove ourselves as the current executor, notifying any // Remove ourselves as the current executor, notifying any
// other threads that are waiting. // other threads that are waiting.
currently_executing = self.data.currently_executing.lock(); currently_executing = self.data.currently_executing.lock();
*currently_executing = None; currently_executing.thread = None;
for callback in currently_executing.callbacks_to_remove.drain(..) {
state.callbacks.remove(callback);
}
drop(state);
drop(currently_executing); drop(currently_executing);
self.data.sync.notify_all(); self.data.sync.notify_all();

View file

@ -17,7 +17,7 @@ use kludgine::app::winit::window::CursorIcon;
use kludgine::Color; use kludgine::Color;
use parking_lot::{Mutex, MutexGuard}; use parking_lot::{Mutex, MutexGuard};
use crate::app::{Application, Open, PendingApp, Run}; use crate::app::Run;
use crate::context::sealed::Trackable as _; use crate::context::sealed::Trackable as _;
use crate::context::{ use crate::context::{
AsEventContext, EventContext, GraphicsContext, LayoutContext, ManageWidget, WidgetContext, AsEventContext, EventContext, GraphicsContext, LayoutContext, ManageWidget, WidgetContext,
@ -46,7 +46,7 @@ use crate::widgets::{
}; };
use crate::window::sealed::WindowCommand; use crate::window::sealed::WindowCommand;
use crate::window::{ use crate::window::{
DeviceId, KeyEvent, Rgb8, RunningWindow, StandaloneWindowBuilder, ThemeMode, DeviceId, KeyEvent, MakeWindow, Rgb8, RunningWindow, StandaloneWindowBuilder, ThemeMode,
VirtualRecorderBuilder, Window, WindowBehavior, WindowHandle, WindowLocal, VirtualRecorderBuilder, Window, WindowBehavior, WindowHandle, WindowLocal,
}; };
use crate::ConstraintLimit; use crate::ConstraintLimit;
@ -470,23 +470,6 @@ where
} }
// ANCHOR_END: run // ANCHOR_END: run
impl<T> Open for T
where
T: MakeWidget,
{
fn open<App>(self, app: &mut App) -> crate::Result<crate::window::WindowHandle>
where
App: Application + ?Sized,
{
Window::<WidgetInstance>::new(self.make_widget()).open(app)
}
fn run_in(self, mut app: PendingApp) -> crate::Result {
Window::<WidgetInstance>::new(self.make_widget()).open(&mut app)?;
app.run()
}
}
/// A behavior that should be applied to a root widget. /// A behavior that should be applied to a root widget.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum RootBehavior { pub enum RootBehavior {
@ -918,8 +901,8 @@ pub trait MakeWidget: Sized {
fn make_widget(self) -> WidgetInstance; fn make_widget(self) -> WidgetInstance;
/// Returns a new window containing `self` as the root widget. /// Returns a new window containing `self` as the root widget.
fn into_window(self) -> Window<WidgetInstance> { fn into_window(self) -> Window {
Window::new(self.make_widget()) self.make_window()
} }
/// Returns a builder for a standalone window. /// Returns a builder for a standalone window.
@ -1611,7 +1594,7 @@ impl WidgetInstance {
where where
Self: Clone, Self: Clone,
{ {
self.clone().into_window() self.clone().make_window()
} }
} }

View file

@ -562,7 +562,7 @@ where
} }
} }
impl Window<WidgetInstance> { impl Window {
/// Returns a new instance using `widget` as its contents. /// Returns a new instance using `widget` as its contents.
pub fn for_widget<W>(widget: W) -> Self pub fn for_widget<W>(widget: W) -> Self
where where
@ -645,6 +645,63 @@ where
} }
} }
/// Returns the handle to this window.
pub const fn handle(&self) -> &WindowHandle {
&self.pending.0
}
/// Opens `self` in the center of the monitor the window initially appears
/// on.
pub fn open_centered<App>(mut self, app: &mut App) -> crate::Result<WindowHandle>
where
App: Application + ?Sized,
{
// We want to ensure that if the user has customized any of these
// properties that we keep their dynamic.
let outer_position = self.outer_position.clone().unwrap_or_else(|| {
let outer_position = Dynamic::new(Point::default());
self.outer_position = Some(outer_position.clone());
outer_position
});
let outer_size = self.outer_size.clone().unwrap_or_else(|| {
let outer_size = Dynamic::new(Size::default());
self.outer_size = Some(outer_size.clone());
outer_size
});
let visible = self.visible.clone().unwrap_or_else(|| {
let visible = Dynamic::new(false);
self.visible = Some(visible.clone());
visible
});
visible.set(false);
let callback_handle = Dynamic::new(None);
callback_handle.set(Some(outer_size.for_each_subsequent({
let visible = visible.clone();
let app = app.as_app();
let callback_handle = callback_handle.clone();
move |new_size| {
if let Some(monitor) = app.monitors().and_then(|monitors| {
let initial_position = outer_position.get();
monitors
.available
.into_iter()
.find(|m| m.region().contains(initial_position))
.or(monitors.primary)
}) {
let region = monitor.region();
let margin = region.size - new_size.into_signed();
outer_position.set(region.origin + margin / 2);
}
visible.set(true);
// Uninstall this callback to ensure it doesn't fire again.
let _ = callback_handle.take();
}
})));
self.open(app)
}
/// Sets `focused` to be the dynamic updated when this window's focus status /// Sets `focused` to be the dynamic updated when this window's focus status
/// is changed. /// is changed.
/// ///
@ -936,61 +993,62 @@ where
} }
} }
impl<Behavior> Open for Window<Behavior> impl<T> Open for T
where where
Behavior: WindowBehavior, T: MakeWindow,
{ {
fn open<App>(self, app: &mut App) -> crate::Result<WindowHandle> fn open<App>(self, app: &mut App) -> crate::Result<WindowHandle>
where where
App: Application + ?Sized, App: Application + ?Sized,
{ {
let this = self.make_window();
let cushy = app.cushy().clone(); let cushy = app.cushy().clone();
let handle = self.pending.handle(); let handle = this.pending.handle();
OpenWindow::<Behavior>::open_with( OpenWindow::<T::Behavior>::open_with(
app, app,
sealed::Context { sealed::Context {
user: self.context, user: this.context,
settings: RefCell::new(sealed::WindowSettings { settings: RefCell::new(sealed::WindowSettings {
cushy, cushy,
title: self.title, title: this.title,
redraw_status: self.pending.0.redraw_status.clone(), redraw_status: this.pending.0.redraw_status.clone(),
on_open: self.on_open, on_open: this.on_open,
on_closed: self.on_closed, on_closed: this.on_closed,
transparent: self.attributes.transparent, transparent: this.attributes.transparent,
attributes: Some(self.attributes), attributes: Some(this.attributes),
occluded: self.occluded.unwrap_or_default(), occluded: this.occluded.unwrap_or_default(),
focused: self.focused.unwrap_or_default(), focused: this.focused.unwrap_or_default(),
inner_size: self.inner_size.unwrap_or_default(), inner_size: this.inner_size.unwrap_or_default(),
theme: Some(self.theme), theme: Some(this.theme),
theme_mode: self.theme_mode, theme_mode: this.theme_mode,
font_data_to_load: self.fonts, font_data_to_load: this.fonts,
serif_font_family: self.serif_font_family, serif_font_family: this.serif_font_family,
sans_serif_font_family: self.sans_serif_font_family, sans_serif_font_family: this.sans_serif_font_family,
fantasy_font_family: self.fantasy_font_family, fantasy_font_family: this.fantasy_font_family,
monospace_font_family: self.monospace_font_family, monospace_font_family: this.monospace_font_family,
cursive_font_family: self.cursive_font_family, cursive_font_family: this.cursive_font_family,
vsync: self.vsync, vsync: this.vsync,
multisample_count: self.multisample_count, multisample_count: this.multisample_count,
close_requested: self.close_requested.map(|cb| Arc::new(Mutex::new(cb))), close_requested: this.close_requested.map(|cb| Arc::new(Mutex::new(cb))),
zoom: self.zoom.unwrap_or_else(|| Dynamic::new(Fraction::ONE)), zoom: this.zoom.unwrap_or_else(|| Dynamic::new(Fraction::ONE)),
resize_to_fit: self.resize_to_fit, resize_to_fit: this.resize_to_fit,
content_protected: self.content_protected.unwrap_or_default(), content_protected: this.content_protected.unwrap_or_default(),
cursor_hittest: self.cursor_hittest.unwrap_or_else(|| Value::Constant(true)), cursor_hittest: this.cursor_hittest.unwrap_or_else(|| Value::Constant(true)),
cursor_visible: self.cursor_visible.unwrap_or_else(|| Value::Constant(true)), cursor_visible: this.cursor_visible.unwrap_or_else(|| Value::Constant(true)),
cursor_position: self.cursor_position.unwrap_or_default(), cursor_position: this.cursor_position.unwrap_or_default(),
window_level: self.window_level.unwrap_or_default(), window_level: this.window_level.unwrap_or_default(),
decorated: self.decorated.unwrap_or_else(|| Value::Constant(true)), decorated: this.decorated.unwrap_or_else(|| Value::Constant(true)),
maximized: self.maximized.unwrap_or_default(), maximized: this.maximized.unwrap_or_default(),
minimized: self.minimized.unwrap_or_default(), minimized: this.minimized.unwrap_or_default(),
resizable: self.resizable.unwrap_or_else(|| Value::Constant(true)), resizable: this.resizable.unwrap_or_else(|| Value::Constant(true)),
resize_increments: self.resize_increments.unwrap_or_default(), resize_increments: this.resize_increments.unwrap_or_default(),
visible: self.visible.unwrap_or_default(), visible: this.visible.unwrap_or_default(),
inner_position: self.inner_position.unwrap_or_default(), inner_position: this.inner_position.unwrap_or_default(),
outer_position: self.outer_position.unwrap_or_default(), outer_position: this.outer_position.unwrap_or_default(),
outer_size: self.outer_size.unwrap_or_default(), outer_size: this.outer_size.unwrap_or_default(),
window_icon: self.icon.unwrap_or_default(), window_icon: this.icon.unwrap_or_default(),
}), }),
pending: self.pending, pending: this.pending,
}, },
)?; )?;
@ -1003,6 +1061,66 @@ where
} }
} }
/// A type that can be made into a [`Window`].
pub trait MakeWindow {
/// The behavior associated with this window.
type Behavior: WindowBehavior;
/// Returns a new window from `self`.
fn make_window(self) -> Window<Self::Behavior>;
/// Opens `self` in the center of the monitor the window initially appears
/// on.
fn open_centered<App>(self, app: &mut App) -> crate::Result<WindowHandle>
where
Self: Sized,
App: Application + ?Sized,
{
self.make_window().open_centered(app)
}
/// Runs `self` in the center of the monitor the window
/// initially appears on.
fn run_centered(self) -> crate::Result
where
Self: Sized,
{
self.make_window().run()
}
/// Runs `app` after opening `self` in the center of the monitor the window
/// initially appears on.
fn run_centered_in(self, mut app: PendingApp) -> crate::Result
where
Self: Sized,
{
self.make_window().open_centered(&mut app)?;
app.run()
}
}
impl<Behavior> MakeWindow for Window<Behavior>
where
Behavior: WindowBehavior,
{
type Behavior = Behavior;
fn make_window(self) -> Window<Self::Behavior> {
self
}
}
impl<T> MakeWindow for T
where
T: MakeWidget,
{
type Behavior = WidgetInstance;
fn make_window(self) -> Window<Self::Behavior> {
Window::for_widget(self.make_widget())
}
}
/// The behavior of a Cushy window. /// The behavior of a Cushy window.
pub trait WindowBehavior: Sized + 'static { pub trait WindowBehavior: Sized + 'static {
/// The type that is provided when initializing this window. /// The type that is provided when initializing this window.
@ -2863,7 +2981,7 @@ impl PendingWindow {
} }
/// Returns a [`Window`] containing `root`. /// Returns a [`Window`] containing `root`.
pub fn with_root(self, root: impl MakeWidget) -> Window<WidgetInstance> { pub fn with_root(self, root: impl MakeWidget) -> Window {
Window::new_with_pending(root.make_widget(), self) Window::new_with_pending(root.make_widget(), self)
} }