Added most window settings from winit

Refs #160
This commit is contained in:
Jonathan Johnson 2024-09-05 20:31:01 -07:00
parent 9304165981
commit 634a4b7af8
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
4 changed files with 702 additions and 69 deletions

View file

@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `PendingWindow`s opened into a `PendingApp` now have working `WindowHandle`s.
As a result of this fix, `Open::open()` now returns a `WindowHandle` instead
of an `Option<WindowHandle>`.
- `CushyWindow::set_occluded` and `CushyWindow::resize now require a
`PlatformWindowImplementation` parameter.
### Fixed
@ -51,6 +53,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Window::resize_to_fit`
- `StandaloneWindowBuilder::resize_to_fit`
- `Tracked<Source>` is a new type that can store a `Dynamic<T>` or `Value<T>`
and provide easy ways to track whether the value has been updated.
- Many new functions have been added to `Window` to expose more functionality
supported by winit:
- `Window::content_protected`
- `Window::cursor_hittest`
- `Window::cursor_visible`
- `Window::cursor_position`
- `Window::decorated`
- `Window::window_level`
- `Window::minimized`
- `Window::maximized`
- `Window::resized`
- `Window::resize_increments`
- `Window::transparent`
- `Window::visible`
[139]: https://github.com/khonsulabs/cushy/issues/139

View file

@ -1425,12 +1425,14 @@ pub(crate) mod sealed {
pub trait Trackable {
fn inner_redraw_when_changed(&self, handle: WindowHandle);
fn inner_sync_when_changed(&self, handle: WindowHandle);
fn inner_invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId);
}
#[derive(Debug, Default, Clone)]
pub struct InvalidationStatus {
refresh_sent: Arc<AtomicBool>,
sync_sent: Arc<AtomicBool>,
invalidated: Arc<Mutex<Set<WidgetId>>>,
}
@ -1441,10 +1443,20 @@ pub(crate) mod sealed {
.is_ok()
}
pub fn should_send_sync(&self) -> bool {
self.sync_sent
.compare_exchange(false, true, Ordering::Release, Ordering::Acquire)
.is_ok()
}
pub fn refresh_received(&self) {
self.refresh_sent.store(false, Ordering::Release);
}
pub fn sync_received(&self) {
self.sync_sent.store(false, Ordering::Release);
}
pub fn invalidate(&self, widget: WidgetId) -> bool {
let mut invalidated = self.invalidated.lock();
invalidated.insert(widget)

View file

@ -12,7 +12,7 @@ use std::task::{Poll, Waker};
use std::thread::{self, ThreadId};
use std::time::{Duration, Instant};
use ahash::AHashSet;
use ahash::{AHashMap, AHashSet};
use alot::{LotId, Lots};
use intentional::Assert;
use kempt::{Map, Sort};
@ -1352,6 +1352,10 @@ impl<T> context::sealed::Trackable for Dynamic<T> {
self.0.redraw_when_changed(handle);
}
fn inner_sync_when_changed(&self, handle: WindowHandle) {
self.0.sync_when_changed(handle);
}
fn inner_invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId) {
self.0.invalidate_when_changed(handle, id);
}
@ -1509,7 +1513,12 @@ impl<T> DynamicData<T> {
pub fn redraw_when_changed(&self, window: WindowHandle) {
let mut state = self.state().expect("deadlocked");
state.invalidation.windows.insert(window);
state.invalidation.windows.insert(window, true);
}
pub fn sync_when_changed(&self, window: WindowHandle) {
let mut state = self.state().expect("deadlocked");
state.invalidation.windows.entry(window).or_insert(false);
}
pub fn invalidate_when_changed(&self, window: WindowHandle, widget: WidgetId) {
@ -1775,7 +1784,7 @@ impl AddAssign for CallbackHandle {
#[derive(Default)]
struct InvalidationState {
windows: AHashSet<WindowHandle>,
windows: AHashMap<WindowHandle, bool>,
widgets: AHashSet<(WindowHandle, WidgetId)>,
wakers: Vec<Waker>,
}
@ -1785,8 +1794,12 @@ impl InvalidationState {
for (window, widget) in self.widgets.drain() {
window.invalidate(widget);
}
for window in self.windows.drain() {
window.redraw();
for (window, redraw) in self.windows.drain() {
if redraw {
window.redraw();
} else {
window.sync();
}
}
for waker in self.wakers.drain(..) {
waker.wake();
@ -1827,7 +1840,7 @@ impl<T> State<T> {
},
callbacks: Arc::default(),
invalidation: InvalidationState {
windows: AHashSet::new(),
windows: AHashMap::new(),
wakers: Vec::new(),
widgets: AHashSet::new(),
},
@ -2341,6 +2354,10 @@ impl<T> context::sealed::Trackable for DynamicReader<T> {
self.source.redraw_when_changed(handle);
}
fn inner_sync_when_changed(&self, handle: WindowHandle) {
self.source.sync_when_changed(handle);
}
fn inner_invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId) {
self.source.invalidate_when_changed(handle, id);
}
@ -2858,6 +2875,12 @@ impl<T> crate::context::sealed::Trackable for ReadOnly<T> {
}
}
fn inner_sync_when_changed(&self, handle: WindowHandle) {
if let ReadOnly::Reader(dynamic) = self {
dynamic.inner_sync_when_changed(handle);
}
}
fn inner_redraw_when_changed(&self, handle: WindowHandle) {
if let ReadOnly::Reader(dynamic) = self {
dynamic.inner_redraw_when_changed(handle);
@ -2872,6 +2895,12 @@ impl<T> crate::context::sealed::Trackable for Value<T> {
}
}
fn inner_sync_when_changed(&self, handle: WindowHandle) {
if let Value::Dynamic(dynamic) = self {
dynamic.inner_sync_when_changed(handle);
}
}
fn inner_redraw_when_changed(&self, handle: WindowHandle) {
if let Value::Dynamic(dynamic) = self {
dynamic.inner_redraw_when_changed(handle);
@ -3840,6 +3869,242 @@ impl Watcher {
}
}
/// A value that has its read and updated states tracked.
pub struct Tracked<Source>
where
Source: TrackedSource,
{
current: Source::Value,
source: Source,
current_generation: Generation,
unread: bool,
}
impl<Source> Tracked<Source>
where
Source: TrackedSource,
{
/// Returns a new tracked instance.
pub fn new(source: Source) -> Self {
let (current, current_generation) = source.read();
Self {
current,
current_generation,
source,
unread: true,
}
}
/// Updates this tracked instance's cached value from the source.
///
/// Returns true if a value hasn't been read yet.
pub fn update(&mut self) -> bool {
if let Some((updated, updated_generation)) =
self.source.read_if_needed(self.current_generation)
{
self.current = updated;
self.current_generation = updated_generation;
self.unread = true;
true
} else {
self.unread
}
}
/// Returns the current value from the source, if it hasn't been read
/// before.
pub fn updated(&mut self) -> Option<&Source::Value> {
if self.update() {
self.unread = false;
Some(&self.current)
} else {
None
}
}
/// Returns true if the currently cached value hasn't been read.
pub const fn unread(&self) -> bool {
self.unread
}
/// Reads the current value from the source.
pub fn read(&mut self) -> &Source::Value {
self.update();
self.read_cached()
}
/// Reads the current cached value.
///
/// This function does not check if an updated value exists in the source.
pub fn read_cached(&mut self) -> &Source::Value {
self.unread = false;
self.peek()
}
/// Returns the current cached value without changing the unread state.
pub const fn peek(&self) -> &Source::Value {
&self.current
}
/// Returns the source being tracked.
pub const fn source(&self) -> &Source {
&self.source
}
/// Marks the current value in the source as being read.
pub fn mark_read(&mut self) {
self.unread = false;
self.current_generation = self.source.generation();
}
/// Updates the value stored in the source, and marks it as being read.
pub fn set_and_read(&mut self, new_value: Source::Value)
where
Source::Value: PartialEq + Clone,
{
self.current = new_value;
self.unread = false;
if self.source.set(self.current.clone()) {
self.current_generation = self.source.generation();
}
}
}
/// A [`Source`] that can be used in a [`Tracked`] instance.
pub trait TrackedSource: sealed::TrackedSource {}
mod sealed {
use super::Generation;
pub trait TrackedSource {
type Value;
fn read(&self) -> (Self::Value, Generation);
fn read_if_needed(&self, read_generation: Generation) -> Option<(Self::Value, Generation)>;
fn generation(&self) -> Generation;
fn set(&self, new_value: Self::Value) -> bool;
}
}
impl<T> TrackedSource for Dynamic<T> where T: Clone + PartialEq {}
impl<T> sealed::TrackedSource for Dynamic<T>
where
T: Clone + PartialEq,
{
type Value = T;
fn read(&self) -> (Self::Value, Generation) {
self.map_generational(|g| (g.clone(), g.generation()))
}
fn read_if_needed(&self, read_generation: Generation) -> Option<(Self::Value, Generation)> {
self.map_generational(|g| {
if g.generation() == read_generation {
None
} else {
Some((g.clone(), g.generation()))
}
})
}
fn generation(&self) -> Generation {
Source::generation(self)
}
fn set(&self, new_value: Self::Value) -> bool {
let mut value = self.lock();
if *value == new_value {
false
} else {
*value = new_value;
true
}
}
}
impl<T> TrackedSource for Value<T> where T: Clone + PartialEq {}
impl<T> sealed::TrackedSource for Value<T>
where
T: Clone + PartialEq,
{
type Value = T;
fn read(&self) -> (Self::Value, Generation) {
match self {
Value::Constant(value) => (value.clone(), Generation::default()),
Value::Dynamic(value) => sealed::TrackedSource::read(value),
}
}
fn read_if_needed(&self, read_generation: Generation) -> Option<(Self::Value, Generation)> {
match self {
Value::Constant(_) => None,
Value::Dynamic(value) => sealed::TrackedSource::read_if_needed(value, read_generation),
}
}
fn generation(&self) -> Generation {
match self {
Value::Constant(_) => Generation::default(),
Value::Dynamic(value) => Source::generation(value),
}
}
fn set(&self, new_value: Self::Value) -> bool {
match self {
Value::Constant(_) => false,
Value::Dynamic(value) => sealed::TrackedSource::set(value, new_value),
}
}
}
impl<S> Destination<S::Value> for Tracked<S>
where
S: TrackedSource + Destination<S::Value>,
{
fn try_map_mut<R>(
&self,
map: impl FnOnce(Mutable<'_, S::Value>) -> R,
) -> Result<R, DeadlockError> {
self.source.try_map_mut(map)
}
}
impl<T> From<Dynamic<T>> for Tracked<Dynamic<T>>
where
T: Clone + PartialEq,
{
fn from(source: Dynamic<T>) -> Self {
Self::new(source)
}
}
impl<T> From<Value<T>> for Tracked<Value<T>>
where
T: Clone + PartialEq,
{
fn from(source: Value<T>) -> Self {
Self::new(source)
}
}
impl<Source> context::sealed::Trackable for Tracked<Source>
where
Source: Trackable + TrackedSource,
{
fn inner_invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId) {
self.source.inner_invalidate_when_changed(handle, id);
}
fn inner_sync_when_changed(&self, handle: WindowHandle) {
self.source.inner_sync_when_changed(handle);
}
fn inner_redraw_when_changed(&self, handle: WindowHandle) {
self.source.inner_redraw_when_changed(handle);
}
}
#[test]
fn map_cycle_is_finite() {
crate::initialize_tracing();

View file

@ -19,7 +19,8 @@ use alot::LotId;
use arboard::Clipboard;
use figures::units::{Px, UPx};
use figures::{
Fraction, IntoSigned, IntoUnsigned, Point, Ranged, Rect, Round, ScreenScale, Size, UPx2D, Zero,
FloatConversion, Fraction, IntoSigned, IntoUnsigned, Point, Ranged, Rect, Round, ScreenScale,
Size, UPx2D, Zero,
};
use image::{DynamicImage, RgbImage, RgbaImage};
use intentional::{Assert, Cast};
@ -30,7 +31,7 @@ use kludgine::app::winit::event::{
use kludgine::app::winit::keyboard::{
Key, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey, SmolStr,
};
use kludgine::app::winit::window::{self, Cursor};
use kludgine::app::winit::window::{self, Cursor, WindowLevel};
use kludgine::app::{winit, WindowBehavior as _};
use kludgine::cosmic_text::{fontdb, Family, FamilyOwned};
use kludgine::drawing::Drawing;
@ -38,6 +39,7 @@ use kludgine::shapes::Shape;
use kludgine::wgpu::{self, CompositeAlphaMode, COPY_BYTES_PER_ROW_ALIGNMENT};
use kludgine::{Color, DrawableExt, Kludgine, KludgineId, Origin, Texture};
use parking_lot::{Mutex, MutexGuard};
use sealed::Ize;
use tracing::Level;
use unicode_segmentation::UnicodeSegmentation;
@ -45,7 +47,7 @@ use crate::animation::{
AnimationTarget, Easing, LinearInterpolate, PercentBetween, Spawn, ZeroToOne,
};
use crate::app::{Application, Cushy, Open, PendingApp, Run};
use crate::context::sealed::InvalidationStatus;
use crate::context::sealed::{InvalidationStatus, Trackable as _};
use crate::context::{
AsEventContext, EventContext, Exclusive, GraphicsContext, LayoutContext, Trackable,
WidgetContext,
@ -56,8 +58,7 @@ use crate::styles::{Edges, FontFamilyList, ThemePair};
use crate::tree::Tree;
use crate::utils::ModifiersExt;
use crate::value::{
Destination, Dynamic, DynamicRead, DynamicReader, Generation, IntoDynamic, IntoValue, Source,
Value,
Destination, Dynamic, DynamicReader, IntoDynamic, IntoValue, Source, Tracked, Value,
};
use crate::widget::{
Callback, EventHandling, MakeWidget, MountedWidget, OnceCallback, RootBehavior, WidgetId,
@ -160,6 +161,14 @@ pub trait PlatformWindowImplementation {
winit.set_max_inner_size::<PhysicalSize<u32>>(max_size.map(Into::into));
}
}
/// Ensures that this window will be redrawn when `value` has been updated.
fn redraw_when_changed(&self, value: &impl Trackable, invalidation_status: &InvalidationStatus)
where
Self: Sized,
{
value.inner_redraw_when_changed(self.handle(invalidation_status.clone()));
}
}
impl PlatformWindowImplementation for kludgine::app::Window<'_, WindowCommand> {
@ -507,6 +516,17 @@ where
occluded: Option<Dynamic<bool>>,
focused: Option<Dynamic<bool>>,
theme_mode: Option<Value<ThemeMode>>,
content_protected: Option<Value<bool>>,
cursor_hittest: Option<Value<bool>>,
cursor_visible: Option<Value<bool>>,
cursor_position: Option<Dynamic<Point<Px>>>,
window_level: Option<Value<WindowLevel>>,
decorated: Option<Value<bool>>,
maximized: Option<Dynamic<bool>>,
minimized: Option<Dynamic<bool>>,
resizable: Option<Value<bool>>,
resize_increments: Option<Value<Size<UPx>>>,
visible: Option<Dynamic<bool>>,
close_requested: Option<Callback<(), bool>>,
}
@ -584,6 +604,17 @@ where
close_requested: None,
zoom: None,
resize_to_fit: Value::Constant(false),
content_protected: None,
cursor_hittest: None,
cursor_visible: None,
cursor_position: None,
window_level: None,
decorated: None,
maximized: None,
minimized: None,
resizable: None,
resize_increments: None,
visible: None,
}
}
@ -635,6 +666,84 @@ where
self
}
/// Prevents the window contents from being captured by other apps.
pub fn content_protected(mut self, protected: impl IntoValue<bool>) -> Self {
self.content_protected = Some(protected.into_value());
self
}
/// Controls whether the cursor should interact with this window or not.
pub fn cursor_hittest(mut self, hittest: impl IntoValue<bool>) -> Self {
self.cursor_hittest = Some(hittest.into_value());
self
}
/// Sets whether the cursor is visible when above this window.
pub fn cursor_visible(mut self, visible: impl IntoValue<bool>) -> Self {
self.cursor_visible = Some(visible.into_value());
self
}
/// A dynamic providing access to the window coordinate of the cursor, or
/// -1, -1 if the cursor is not currently hovering the window.
///
/// In the future, this dynamic will also support setting the position of
/// the cursor within the window.
pub fn cursor_position(mut self, window_position: impl IntoDynamic<Point<Px>>) -> Self {
self.cursor_position = Some(window_position.into_dynamic());
self
}
/// Controls whether window decorations are shown around this window.
pub fn decorated(mut self, decorated: impl IntoValue<bool>) -> Self {
self.decorated = Some(decorated.into_value());
self
}
/// Controls the level of this window.
pub fn window_level(mut self, window_level: impl IntoValue<WindowLevel>) -> Self {
self.window_level = Some(window_level.into_value());
self
}
/// Provides a dynamic that is updated with the minimized status of this
/// window.
pub fn minimized(mut self, minimized: impl IntoDynamic<bool>) -> Self {
self.minimized = Some(minimized.into_dynamic());
self
}
/// Provides a dynamic that is updated with the maximized status of this
/// window.
pub fn maximized(mut self, maximized: impl IntoDynamic<bool>) -> Self {
self.maximized = Some(maximized.into_dynamic());
self
}
/// Controls whether the window is resizable by the user or not.
pub fn resizable(mut self, resizable: impl IntoValue<bool>) -> Self {
self.resizable = Some(resizable.into_value());
self
}
/// Controls the increments in which the window can be resized.
pub fn resize_increments(mut self, resize_increments: impl IntoValue<Size<UPx>>) -> Self {
self.resize_increments = Some(resize_increments.into_value());
self
}
/// Sets this window to render with a transparent background.
pub fn transparent(mut self) -> Self {
self.attributes.transparent = true;
self
}
/// Controls the visibility of this window.
pub fn visible(mut self, visible: impl IntoDynamic<bool>) -> Self {
self.visible = Some(visible.into_dynamic());
self
}
/// Sets this window's `zoom` factor.
///
/// The zoom factor is multiplied with the DPI scaling from the window
@ -754,6 +863,17 @@ where
close_requested: self.close_requested.map(|cb| Arc::new(Mutex::new(cb))),
zoom: self.zoom.unwrap_or_else(|| Dynamic::new(Fraction::ONE)),
resize_to_fit: self.resize_to_fit,
content_protected: self.content_protected.unwrap_or_default(),
cursor_hittest: self.cursor_hittest.unwrap_or_else(|| Value::Constant(true)),
cursor_visible: self.cursor_visible.unwrap_or_else(|| Value::Constant(true)),
cursor_position: self.cursor_position.unwrap_or_default(),
window_level: self.window_level.unwrap_or_default(),
decorated: self.decorated.unwrap_or_else(|| Value::Constant(true)),
maximized: self.maximized.unwrap_or_default(),
minimized: self.minimized.unwrap_or_default(),
resizable: self.resizable.unwrap_or_else(|| Value::Constant(true)),
resize_increments: self.resize_increments.unwrap_or_default(),
visible: self.visible.unwrap_or_default(),
}),
pending: self.pending,
},
@ -820,8 +940,7 @@ struct OpenWindow<T> {
initial_frame: bool,
occluded: Dynamic<bool>,
focused: Dynamic<bool>,
inner_size: Dynamic<Size<UPx>>,
inner_size_generation: Generation,
inner_size: Tracked<Dynamic<Size<UPx>>>,
keyboard_activated: Option<WidgetId>,
min_inner_size: Option<Size<UPx>>,
max_inner_size: Option<Size<UPx>>,
@ -835,9 +954,19 @@ struct OpenWindow<T> {
on_closed: Option<OnceCallback>,
vsync: bool,
dpi_scale: Dynamic<Fraction>,
zoom: Dynamic<Fraction>,
zoom_generation: Generation,
zoom: Tracked<Dynamic<Fraction>>,
close_requested: Option<Arc<Mutex<Callback<(), bool>>>>,
content_protected: Tracked<Value<bool>>,
cursor_hittest: Tracked<Value<bool>>,
cursor_visible: Tracked<Value<bool>>,
cursor_position: Tracked<Dynamic<Point<Px>>>,
window_level: Tracked<Value<WindowLevel>>,
decorated: Tracked<Value<bool>>,
maximized: Tracked<Dynamic<bool>>,
minimized: Tracked<Dynamic<bool>>,
resizable: Tracked<Value<bool>>,
resize_increments: Tracked<Value<Size<UPx>>>,
visible: Tracked<Dynamic<bool>>,
}
impl<T> OpenWindow<T>
@ -1200,19 +1329,12 @@ where
cushy.fonts.clone(),
graphics.font_system().db_mut(),
);
let occluded = settings.occluded;
let focused = settings.focused;
let theme = settings.theme.unwrap_or_default();
let inner_size = settings.inner_size;
let on_closed = settings.on_closed;
let close_requested = settings.close_requested;
let vsync = settings.vsync;
let zoom = settings.zoom;
let resize_to_fit = settings.resize_to_fit;
let dpi_scale = Dynamic::new(graphics.dpi_scale());
inner_size.set(window.inner_size());
let dpi_scale = Dynamic::new(graphics.dpi_scale());
let theme_mode = match settings.theme_mode.take() {
Some(Value::Dynamic(dynamic)) => {
dynamic.set(window.theme().into());
@ -1221,11 +1343,11 @@ where
Some(Value::Constant(mode)) => Value::Constant(mode),
None => Value::dynamic(window.theme().into()),
};
let transparent = settings.transparent;
let tree = Tree::default();
let root = tree.push_boxed(behavior.make_root(), None);
let theme = settings.theme.unwrap_or_default();
let (current_theme, theme) = match theme {
Value::Constant(theme) => (theme, None),
Value::Dynamic(dynamic) => (dynamic.get(), Some(dynamic.into_reader())),
@ -1244,26 +1366,35 @@ where
mouse_buttons: AHashMap::default(),
redraw_status,
initial_frame: true,
occluded,
focused,
inner_size_generation: inner_size.generation(),
inner_size,
occluded: settings.occluded,
focused: settings.focused,
inner_size: Tracked::from(inner_size),
keyboard_activated: None,
min_inner_size: None,
max_inner_size: None,
resize_to_fit,
resize_to_fit: settings.resize_to_fit,
current_theme,
theme,
theme_mode,
transparent,
transparent: settings.transparent,
fonts,
cushy,
on_closed,
vsync,
close_requested,
on_closed: settings.on_closed,
vsync: settings.vsync,
close_requested: settings.close_requested,
dpi_scale,
zoom_generation: zoom.generation(),
zoom,
zoom: Tracked::from(settings.zoom),
content_protected: Tracked::from(settings.content_protected),
cursor_hittest: Tracked::from(settings.cursor_hittest),
cursor_visible: Tracked::from(settings.cursor_visible),
cursor_position: Tracked::from(settings.cursor_position),
window_level: Tracked::from(settings.window_level),
decorated: Tracked::from(settings.decorated),
maximized: Tracked::from(settings.maximized),
minimized: Tracked::from(settings.minimized),
resizable: Tracked::from(settings.resizable),
resize_increments: Tracked::from(settings.resize_increments),
visible: Tracked::from(settings.visible),
}
}
@ -1277,25 +1408,23 @@ where
self.redraw_status.refresh_received();
graphics.reset_text_attributes();
let zoom = self.zoom.read();
if zoom.generation() != self.zoom_generation {
if let Some(zoom) = self.zoom.updated() {
graphics.set_zoom(*zoom);
self.redraw_status.invalidate(self.root.id());
}
self.tree
.new_frame(self.redraw_status.invalidations().drain());
drop(zoom);
}
fn prepare<W>(&mut self, window: W, graphics: &mut kludgine::Graphics<'_>)
fn prepare<W>(&mut self, mut window: W, graphics: &mut kludgine::Graphics<'_>)
where
W: PlatformWindowImplementation,
{
let cushy = self.cushy.clone();
let _guard = cushy.enter_runtime();
self.synchronize_platform_window(&mut window);
self.new_frame(graphics);
let resize_to_fit = self.resize_to_fit.get();
@ -1307,7 +1436,7 @@ where
&self.cushy,
&self.focused,
&self.occluded,
&self.inner_size,
self.inner_size.source(),
&self.close_requested,
);
let root_mode = self.constrain_window_resizing(resizable, &mut window, graphics);
@ -1357,10 +1486,8 @@ where
let render_size = actual_size.min(window_size);
layout_context.redraw_when_changed(&self.inner_size);
layout_context.invalidate_when_changed(&self.resize_to_fit);
let inner_size_generation = self.inner_size.generation();
if self.inner_size_generation != inner_size_generation {
layout_context.request_inner_size(self.inner_size.get());
self.inner_size_generation = inner_size_generation;
if let Some(new_size) = self.inner_size.updated() {
layout_context.request_inner_size(*new_size);
} else if actual_size != window_size && !resizable {
let mut new_size = actual_size;
if let Some(min_size) = self.min_inner_size {
@ -1407,7 +1534,7 @@ where
&self.cushy,
&self.focused,
&self.occluded,
&self.inner_size,
self.inner_size.source(),
&self.close_requested,
)) {
self.should_close = true;
@ -1417,19 +1544,101 @@ where
}
}
fn resized(&mut self, new_size: Size<UPx>) {
fn resized<W>(&mut self, new_size: Size<UPx>, window: &W)
where
W: PlatformWindowImplementation,
{
self.inner_size.set(new_size);
// We want to prevent a resize request for this resized event.
self.inner_size_generation = self.inner_size.generation();
self.inner_size.mark_read();
self.update_ized(window);
self.root.invalidate();
}
fn update_ized<W>(&mut self, window: &W)
where
W: PlatformWindowImplementation,
{
if let Some(winit) = window.winit() {
// TODO should these be supported outside of winit? Put in a feature
// request if you read this and need them.
self.maximized.set_and_read(winit.is_maximized());
if let Some(minimized) = winit.is_minimized() {
self.minimized.set_and_read(minimized);
}
self.decorated.set_and_read(winit.is_decorated());
}
}
fn synchronize_platform_window<W>(&mut self, window: &mut W)
where
W: PlatformWindowImplementation,
{
self.update_ized(window);
if let Some(winit) = window.winit() {
self.content_protected
.inner_sync_when_changed(window.handle(self.redraw_status.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()));
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()));
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()));
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()));
if let Some(decorated) = self.decorated.updated() {
winit.set_decorations(*decorated);
}
self.resize_increments
.inner_sync_when_changed(window.handle(self.redraw_status.clone()));
if let Some(resize_increments) = self.resize_increments.updated() {
let increments: Option<PhysicalSize<f32>> =
if resize_increments.width > 0 || resize_increments.height > 0 {
Some(PhysicalSize::new(
resize_increments.width.into_float(),
resize_increments.height.into_float(),
))
} else {
None
};
winit.set_resize_increments(increments);
}
self.visible
.inner_sync_when_changed(window.handle(self.redraw_status.clone()));
if let Some(visible) = self.visible.updated() {
winit.set_visible(*visible);
}
self.resizable
.inner_sync_when_changed(window.handle(self.redraw_status.clone()));
if let Some(resizable) = self.resizable.updated() {
winit.set_resizable(*resizable);
window.set_needs_redraw();
}
}
}
pub fn set_focused(&mut self, focused: bool) {
self.focused.set(focused);
}
pub fn set_occluded(&mut self, occluded: bool) {
pub fn set_occluded<W>(&mut self, window: &W, occluded: bool)
where
W: PlatformWindowImplementation,
{
self.occluded.set(occluded);
self.update_ized(window);
}
pub fn keyboard_input<W>(
@ -1452,7 +1661,7 @@ where
&self.cushy,
&self.focused,
&self.occluded,
&self.inner_size,
self.inner_size.source(),
&self.close_requested,
);
let target = self.tree.focused_widget().unwrap_or(self.root.node_id);
@ -1503,7 +1712,7 @@ where
&self.cushy,
&self.focused,
&self.occluded,
&self.inner_size,
self.inner_size.source(),
&self.close_requested,
);
let widget = self
@ -1547,7 +1756,7 @@ where
&self.cushy,
&self.focused,
&self.occluded,
&self.inner_size,
self.inner_size.source(),
&self.close_requested,
);
let widget = self
@ -1592,12 +1801,13 @@ where
&self.cushy,
&self.focused,
&self.occluded,
&self.inner_size,
self.inner_size.source(),
&self.close_requested,
);
let location = position.into();
self.cursor.location = Some(location);
self.cursor_position.set_and_read(location);
EventContext::new(
WidgetContext::new(
@ -1643,6 +1853,9 @@ where
{
let cushy = self.cushy.clone();
let _guard = cushy.enter_runtime();
self.cursor.location = None;
self.cursor_position
.set_and_read(Point::squared(Px::new(-1)));
if self.cursor.widget.take().is_some() {
let mut window = RunningWindow::new(
window,
@ -1651,7 +1864,7 @@ where
&self.cushy,
&self.focused,
&self.occluded,
&self.inner_size,
self.inner_size.source(),
&self.close_requested,
);
@ -1690,7 +1903,7 @@ where
&self.cushy,
&self.focused,
&self.occluded,
&self.inner_size,
self.inner_size.source(),
&self.close_requested,
);
match state {
@ -1863,7 +2076,7 @@ where
window: kludgine::app::Window<'_, WindowCommand>,
_kludgine: &mut Kludgine,
) {
self.set_occluded(window.ocluded());
self.set_occluded(&window, window.ocluded());
}
fn render<'pass>(
@ -1909,7 +2122,7 @@ where
&self.cushy,
&self.focused,
&self.occluded,
&self.inner_size,
self.inner_size.source(),
&self.close_requested,
),
)
@ -1957,7 +2170,7 @@ where
window: kludgine::app::Window<'_, WindowCommand>,
_kludgine: &mut Kludgine,
) {
self.resized(window.inner_size());
self.resized(window.inner_size(), &window);
}
// fn theme_changed(&mut self, window: kludgine::app::Window<'_, ()>) {}
@ -2059,6 +2272,10 @@ where
WindowCommand::Redraw => {
window.set_needs_redraw();
}
WindowCommand::Sync => {
self.synchronize_platform_window(&mut window);
self.redraw_status.sync_received();
}
WindowCommand::RequestClose => {
let mut window = RunningWindow::new(
window,
@ -2067,7 +2284,7 @@ where
&self.cushy,
&self.focused,
&self.occluded,
&self.inner_size,
self.inner_size.source(),
&self.close_requested,
);
if self.behavior.close_requested(&mut window) {
@ -2077,8 +2294,72 @@ where
WindowCommand::SetTitle(new_title) => {
window.set_title(&new_title);
}
WindowCommand::ResetDeadKeys => {
window.winit().reset_dead_keys();
}
WindowCommand::RequestUserAttention(request_type) => {
window.winit().request_user_attention(request_type);
}
WindowCommand::Focus => {
window.winit().focus_window();
}
WindowCommand::Ize(ize) => {
let (minimize, maximize) = match ize {
Some(Ize::Maximize) => (false, true),
Some(Ize::Minimize) => (true, false),
None => (false, false),
};
if window
.winit()
.is_minimized()
.map_or(true, |minimized| minimized != minimize)
{
window.winit().set_minimized(minimize);
}
if window.winit().is_maximized() != maximize {
window.winit().set_maximized(maximize);
}
}
}
}
// fn dropped_file(
// &mut self,
// window: kludgine::app::Window<'_, WindowCommand>,
// kludgine: &mut Kludgine,
// path: std::path::PathBuf,
// ) {
// }
// fn hovered_file(
// &mut self,
// window: kludgine::app::Window<'_, WindowCommand>,
// kludgine: &mut Kludgine,
// path: std::path::PathBuf,
// ) {
// }
// fn hovered_file_cancelled(
// &mut self,
// window: kludgine::app::Window<'_, WindowCommand>,
// kludgine: &mut Kludgine,
// ) {
// }
// fn received_character(
// &mut self,
// window: kludgine::app::Window<'_, WindowCommand>,
// kludgine: &mut Kludgine,
// char: char,
// ) {
// }
// fn modifiers_changed(
// &mut self,
// window: kludgine::app::Window<'_, WindowCommand>,
// kludgine: &mut Kludgine,
// ) {
// }
}
impl<Behavior> Drop for OpenWindow<Behavior> {
@ -2112,9 +2393,10 @@ pub(crate) mod sealed {
use std::num::NonZeroU32;
use std::sync::Arc;
use figures::units::UPx;
use figures::units::{Px, UPx};
use figures::{Fraction, Point, Size};
use image::DynamicImage;
use kludgine::app::winit::window::{UserAttentionType, WindowLevel};
use kludgine::Color;
use parking_lot::Mutex;
@ -2156,15 +2438,37 @@ pub(crate) mod sealed {
pub multisample_count: NonZeroU32,
pub resize_to_fit: Value<bool>,
pub close_requested: Option<Arc<Mutex<Callback<(), bool>>>>,
pub content_protected: Value<bool>,
pub cursor_hittest: Value<bool>,
pub cursor_visible: Value<bool>,
pub cursor_position: Dynamic<Point<Px>>,
pub window_level: Value<WindowLevel>,
pub decorated: Value<bool>,
pub maximized: Dynamic<bool>,
pub minimized: Dynamic<bool>,
pub resizable: Value<bool>,
pub resize_increments: Value<Size<UPx>>,
pub visible: Dynamic<bool>,
}
#[derive(Debug, Clone)]
pub enum WindowCommand {
Redraw,
Sync,
RequestClose,
ResetDeadKeys,
RequestUserAttention(Option<UserAttentionType>),
Focus,
Ize(Option<Ize>),
SetTitle(String),
}
#[derive(Debug, Clone)]
pub enum Ize {
Maximize,
Minimize,
}
pub trait CaptureFormat {
const HAS_ALPHA: bool;
@ -2312,6 +2616,12 @@ impl WindowHandle {
}
}
pub(crate) fn sync(&self) {
if self.redraw_status.should_send_sync() {
self.inner.send(WindowCommand::Sync);
}
}
/// Marks `widget` as invalidated, and if needed, refreshes the window.
pub fn invalidate(&self, widget: WidgetId) {
if self.redraw_status.invalidate(widget) {
@ -2358,6 +2668,11 @@ impl InnerWindowHandle {
WindowCommand::Redraw => state.redraw_target.set(RedrawTarget::Now),
WindowCommand::RequestClose => state.close_requested.set(true),
WindowCommand::SetTitle(title) => state.title.set(title),
WindowCommand::ResetDeadKeys
| WindowCommand::RequestUserAttention(_)
| WindowCommand::Focus
| WindowCommand::Ize(_)
| WindowCommand::Sync => {}
},
};
}
@ -2730,6 +3045,17 @@ impl StandaloneWindowBuilder {
close_requested: None,
zoom: self.zoom,
resize_to_fit: self.resize_to_fit,
content_protected: Value::Constant(false),
cursor_hittest: Value::Constant(true),
cursor_visible: Value::Constant(true),
cursor_position: Dynamic::default(),
window_level: Value::default(),
decorated: Value::Constant(true),
maximized: Dynamic::new(false),
minimized: Dynamic::new(false),
resizable: Value::Constant(true),
resize_increments: Value::default(),
visible: Dynamic::new(true),
},
);
@ -2846,8 +3172,11 @@ impl CushyWindow {
/// This should only be set to true if the window is not visible at all to
/// the end user due to being offscreen, minimized, or fully hidden behind
/// other windows.
pub fn set_occluded(&mut self, occluded: bool) {
self.window.set_occluded(occluded);
pub fn set_occluded<W>(&mut self, window: &W, occluded: bool)
where
W: PlatformWindowImplementation,
{
self.window.set_occluded(window, occluded);
}
/// Requests that the window close.
@ -2876,15 +3205,18 @@ impl CushyWindow {
}
/// Updates the dimensions and DPI scaling of the window.
pub fn resize(
pub fn resize<W>(
&mut self,
window: &W,
new_size: Size<UPx>,
new_scale: impl Into<Fraction>,
new_zoom: impl Into<Fraction>,
queue: &wgpu::Queue,
) {
) where
W: PlatformWindowImplementation,
{
self.kludgine.resize(new_size, new_scale, new_zoom, queue);
self.window.resized(new_size);
self.window.resized(new_size, window);
}
/// Provide keyboard input to this virtual window.
@ -3070,7 +3402,7 @@ impl VirtualWindow {
/// the end user due to being offscreen, minimized, or fully hidden behind
/// other windows.
pub fn set_occluded(&mut self, occluded: bool) {
self.cushy.set_occluded(occluded);
self.cushy.set_occluded(&&mut self.state, occluded);
}
/// Returns true if this window should no longer be open.
@ -3102,8 +3434,13 @@ impl VirtualWindow {
new_scale: impl Into<Fraction>,
queue: &wgpu::Queue,
) {
self.cushy
.resize(new_size, new_scale, self.cushy.kludgine.zoom(), queue);
self.cushy.resize(
&&mut self.state,
new_size,
new_scale,
self.cushy.kludgine.zoom(),
queue,
);
}
/// Provide keyboard input to this virtual window.