mirror of
https://github.com/danbulant/cushy
synced 2026-07-05 19:20:36 +00:00
Added PendingWindow
Closes #107 This answers the question of how a window can close itself.
This commit is contained in:
parent
17c6f2ef83
commit
8a9fb24fe0
3 changed files with 138 additions and 16 deletions
|
|
@ -69,6 +69,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- `RunningWindow::request_close()` requests that the window should close. This
|
- `RunningWindow::request_close()` requests that the window should close. This
|
||||||
ensures `WindowBehavior::close_requested` is invoked before the window is
|
ensures `WindowBehavior::close_requested` is invoked before the window is
|
||||||
closed.
|
closed.
|
||||||
|
- `PendingWindow` is a new type that can return a `WindowHandle` for a window
|
||||||
|
that hasn't opened yet. This can be used to allow a widget on a window to
|
||||||
|
close the window.
|
||||||
|
|
||||||
[91]: https://github.com/khonsulabs/gooey/issues/91
|
[91]: https://github.com/khonsulabs/gooey/issues/91
|
||||||
[92]: https://github.com/khonsulabs/gooey/issues/92
|
[92]: https://github.com/khonsulabs/gooey/issues/92
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use gooey::kludgine::include_texture;
|
||||||
use gooey::value::{Dynamic, MapEach};
|
use gooey::value::{Dynamic, MapEach};
|
||||||
use gooey::widget::MakeWidget;
|
use gooey::widget::MakeWidget;
|
||||||
use gooey::widgets::Image;
|
use gooey::widgets::Image;
|
||||||
|
use gooey::window::PendingWindow;
|
||||||
use gooey::{Application, Open, PendingApp, Run};
|
use gooey::{Application, Open, PendingApp, Run};
|
||||||
use kludgine::LazyTexture;
|
use kludgine::LazyTexture;
|
||||||
|
|
||||||
|
|
@ -66,14 +67,26 @@ fn open_another_window(
|
||||||
*count += 1;
|
*count += 1;
|
||||||
*count
|
*count
|
||||||
});
|
});
|
||||||
|
|
||||||
let open_windows = open_windows.clone();
|
let open_windows = open_windows.clone();
|
||||||
open_windows.map_mut(|open_windows| *open_windows += 1);
|
open_windows.map_mut(|open_windows| *open_windows += 1);
|
||||||
format!("This is window {my_number}")
|
|
||||||
.and(open_window_button(app, &open_windows, counter, texture))
|
let window = PendingWindow::default();
|
||||||
.and(Image::new(texture.clone()))
|
let handle = window.handle();
|
||||||
.into_rows()
|
|
||||||
.centered()
|
window
|
||||||
.into_window()
|
.with_root(
|
||||||
|
format!("This is window {my_number}")
|
||||||
|
.and(open_window_button(app, &open_windows, counter, texture))
|
||||||
|
.and(Image::new(texture.clone()))
|
||||||
|
.and(
|
||||||
|
"Close"
|
||||||
|
.into_button()
|
||||||
|
.on_click(move |()| handle.request_close()),
|
||||||
|
)
|
||||||
|
.into_rows()
|
||||||
|
.centered(),
|
||||||
|
)
|
||||||
.on_close(move || open_windows.map_mut(|open_windows| *open_windows -= 1))
|
.on_close(move || open_windows.map_mut(|open_windows| *open_windows -= 1))
|
||||||
.open(app)
|
.open(app)
|
||||||
.expect("error opening another window");
|
.expect("error opening another window");
|
||||||
|
|
|
||||||
126
src/window.rs
126
src/window.rs
|
|
@ -6,7 +6,7 @@ use std::hash::Hash;
|
||||||
use std::ops::{Deref, DerefMut, Not};
|
use std::ops::{Deref, DerefMut, Not};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
use std::sync::{MutexGuard, OnceLock};
|
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
|
||||||
|
|
||||||
use ahash::AHashMap;
|
use ahash::AHashMap;
|
||||||
use alot::LotId;
|
use alot::LotId;
|
||||||
|
|
@ -38,8 +38,8 @@ use crate::tree::Tree;
|
||||||
use crate::utils::ModifiersExt;
|
use crate::utils::ModifiersExt;
|
||||||
use crate::value::{Dynamic, DynamicReader, Generation, IntoDynamic, IntoValue, Value};
|
use crate::value::{Dynamic, DynamicReader, Generation, IntoDynamic, IntoValue, Value};
|
||||||
use crate::widget::{
|
use crate::widget::{
|
||||||
EventHandling, MountedWidget, OnceCallback, RootBehavior, Widget, WidgetId, WidgetInstance,
|
EventHandling, MakeWidget, MountedWidget, OnceCallback, RootBehavior, Widget, WidgetId,
|
||||||
HANDLED, IGNORED,
|
WidgetInstance, HANDLED, IGNORED,
|
||||||
};
|
};
|
||||||
use crate::window::sealed::WindowCommand;
|
use crate::window::sealed::WindowCommand;
|
||||||
use crate::{initialize_tracing, ConstraintLimit};
|
use crate::{initialize_tracing, ConstraintLimit};
|
||||||
|
|
@ -144,6 +144,7 @@ where
|
||||||
Behavior: WindowBehavior,
|
Behavior: WindowBehavior,
|
||||||
{
|
{
|
||||||
context: Behavior::Context,
|
context: Behavior::Context,
|
||||||
|
pending: PendingWindow,
|
||||||
/// The attributes of this window.
|
/// The attributes of this window.
|
||||||
pub attributes: WindowAttributes,
|
pub attributes: WindowAttributes,
|
||||||
/// The colors to use to theme the user interface.
|
/// The colors to use to theme the user interface.
|
||||||
|
|
@ -288,6 +289,10 @@ where
|
||||||
/// Returns a new instance using `context` to initialize the window upon
|
/// Returns a new instance using `context` to initialize the window upon
|
||||||
/// opening.
|
/// opening.
|
||||||
pub fn new(context: Behavior::Context) -> Self {
|
pub fn new(context: Behavior::Context) -> Self {
|
||||||
|
Self::new_with_pending(context, PendingWindow::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_with_pending(context: Behavior::Context, pending: PendingWindow) -> Self {
|
||||||
static EXECUTABLE_NAME: OnceLock<String> = OnceLock::new();
|
static EXECUTABLE_NAME: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
let title = EXECUTABLE_NAME
|
let title = EXECUTABLE_NAME
|
||||||
|
|
@ -304,6 +309,7 @@ where
|
||||||
})
|
})
|
||||||
.clone();
|
.clone();
|
||||||
Self {
|
Self {
|
||||||
|
pending,
|
||||||
attributes: WindowAttributes {
|
attributes: WindowAttributes {
|
||||||
title,
|
title,
|
||||||
..WindowAttributes::default()
|
..WindowAttributes::default()
|
||||||
|
|
@ -350,14 +356,14 @@ where
|
||||||
App: Application,
|
App: Application,
|
||||||
{
|
{
|
||||||
let gooey = app.gooey().clone();
|
let gooey = app.gooey().clone();
|
||||||
let redraw_status = InvalidationStatus::default();
|
|
||||||
let handle = GooeyWindow::<Behavior>::open_with(
|
let handle = GooeyWindow::<Behavior>::open_with(
|
||||||
app,
|
app,
|
||||||
sealed::Context {
|
sealed::Context {
|
||||||
user: self.context,
|
user: self.context,
|
||||||
settings: RefCell::new(sealed::WindowSettings {
|
settings: RefCell::new(sealed::WindowSettings {
|
||||||
gooey,
|
gooey,
|
||||||
redraw_status: redraw_status.clone(),
|
redraw_status: self.pending.0.redraw_status.clone(),
|
||||||
on_closed: self.on_closed,
|
on_closed: self.on_closed,
|
||||||
transparent: self.attributes.transparent,
|
transparent: self.attributes.transparent,
|
||||||
attributes: Some(self.attributes),
|
attributes: Some(self.attributes),
|
||||||
|
|
@ -376,7 +382,7 @@ where
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(handle.map(|handle| WindowHandle::new(handle, redraw_status)))
|
Ok(handle.map(|handle| self.pending.opened(handle)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_in(self, app: PendingApp) -> crate::Result {
|
fn run_in(self, app: PendingApp) -> crate::Result {
|
||||||
|
|
@ -1534,7 +1540,7 @@ fn default_family(query: Family<'_>) -> Option<FamilyOwned> {
|
||||||
/// A handle to an open Gooey window.
|
/// A handle to an open Gooey window.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WindowHandle {
|
pub struct WindowHandle {
|
||||||
pub(crate) kludgine: kludgine::app::WindowHandle<WindowCommand>,
|
inner: InnerWindowHandle,
|
||||||
pub(crate) redraw_status: InvalidationStatus,
|
pub(crate) redraw_status: InvalidationStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1544,23 +1550,30 @@ impl WindowHandle {
|
||||||
redraw_status: InvalidationStatus,
|
redraw_status: InvalidationStatus,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kludgine,
|
inner: InnerWindowHandle::Known(kludgine),
|
||||||
redraw_status,
|
redraw_status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pending() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: InnerWindowHandle::Pending(Arc::default()),
|
||||||
|
redraw_status: InvalidationStatus::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Request that the window closes.
|
/// Request that the window closes.
|
||||||
///
|
///
|
||||||
/// A window may disallow itself from being closed by customizing
|
/// A window may disallow itself from being closed by customizing
|
||||||
/// [`WindowBehavior::close_requested`].
|
/// [`WindowBehavior::close_requested`].
|
||||||
pub fn request_close(&self) {
|
pub fn request_close(&self) {
|
||||||
let _result = self.kludgine.send(sealed::WindowCommand::RequestClose);
|
self.inner.send(sealed::WindowCommand::RequestClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests that the window redraws.
|
/// Requests that the window redraws.
|
||||||
pub fn redraw(&self) {
|
pub fn redraw(&self) {
|
||||||
if self.redraw_status.should_send_refresh() {
|
if self.redraw_status.should_send_refresh() {
|
||||||
let _result = self.kludgine.send(WindowCommand::Redraw);
|
self.inner.send(WindowCommand::Redraw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1585,3 +1598,96 @@ impl Hash for WindowHandle {
|
||||||
self.redraw_status.hash(state);
|
self.redraw_status.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum InnerWindowHandle {
|
||||||
|
Pending(Arc<PendingWindowHandle>),
|
||||||
|
Known(kludgine::app::WindowHandle<WindowCommand>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerWindowHandle {
|
||||||
|
fn send(&self, message: WindowCommand) {
|
||||||
|
match self {
|
||||||
|
InnerWindowHandle::Pending(pending) => {
|
||||||
|
if let Some(handle) = pending.handle.get() {
|
||||||
|
let _result = handle.send(message);
|
||||||
|
} else {
|
||||||
|
pending
|
||||||
|
.commands
|
||||||
|
.lock()
|
||||||
|
.expect("lock poisoned")
|
||||||
|
.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InnerWindowHandle::Known(handle) => {
|
||||||
|
let _result = handle.send(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`Window`] that doesn't have its root widget yet.
|
||||||
|
///
|
||||||
|
/// [`PendingWindow::handle()`] returns a handle that allows code to interact
|
||||||
|
/// with a window before it has had its contents initialized. This is useful,
|
||||||
|
/// for example, for a button's `on_click` to be able to close the window that
|
||||||
|
/// contains it.
|
||||||
|
pub struct PendingWindow(WindowHandle);
|
||||||
|
|
||||||
|
impl Default for PendingWindow {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(WindowHandle::pending())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PendingWindow {
|
||||||
|
/// Returns a [`Window`] using `context` to initialize its contents.
|
||||||
|
pub fn with<Behavior>(self, context: Behavior::Context) -> Window<Behavior>
|
||||||
|
where
|
||||||
|
Behavior: WindowBehavior,
|
||||||
|
{
|
||||||
|
Window::new_with_pending(context, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [`Window`] containing `root`.
|
||||||
|
pub fn with_root(self, root: impl MakeWidget) -> Window<WidgetInstance> {
|
||||||
|
Window::new_with_pending(root.make_widget(), self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [`Window`] using the default context to initialize its
|
||||||
|
/// contents.
|
||||||
|
pub fn using<Behavior>(self) -> Window<Behavior>
|
||||||
|
where
|
||||||
|
Behavior: WindowBehavior,
|
||||||
|
Behavior::Context: Default,
|
||||||
|
{
|
||||||
|
self.with(<Behavior::Context>::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a handle for this window.
|
||||||
|
#[must_use]
|
||||||
|
pub fn handle(&self) -> WindowHandle {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opened(self, handle: kludgine::app::WindowHandle<WindowCommand>) -> WindowHandle {
|
||||||
|
let InnerWindowHandle::Pending(pending) = &self.0.inner else {
|
||||||
|
unreachable!("always pending")
|
||||||
|
};
|
||||||
|
|
||||||
|
let initialized = pending.handle.set(handle.clone());
|
||||||
|
assert!(initialized.is_ok());
|
||||||
|
|
||||||
|
for command in pending.commands.lock().expect("poisoned").drain(..) {
|
||||||
|
let _result = handle.send(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowHandle::new(handle, self.0.redraw_status.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct PendingWindowHandle {
|
||||||
|
handle: OnceLock<kludgine::app::WindowHandle<WindowCommand>>,
|
||||||
|
commands: Mutex<Vec<WindowCommand>>,
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue