Repository: zng-ui/zng (language: rust)

Issue:
Implement "enabled_buttons" for Window
Needs view API change. Use [winit](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_enabled_buttons) to implement.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a53aa25a..ecec97283 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,10 +6,12 @@
     - Add `open_title_bar_context_menu`.
     - Rename `close_window` to `close`.
     - Rename `focus_window` to `focus`.
+    - Add `set_enabled_buttons`.
     - Note that this is only a breaking change for direct dependents of `zng-view-api`.
 * Better "custom chrome" example in `examples/window.rs`.
 * Add `OPEN_TITLE_BAR_CONTEXT_MENU_CMD` to window API.
 * Fix `WIDGET.border().offsets()` not including the innermost border offset.
+* Add `WindowVars::enabled_buttons` to window API.
 
 # 0.4.0
 
diff --git a/crates/zng-app/src/view_process.rs b/crates/zng-app/src/view_process.rs
index 0e378615d..1b845fb02 100644
--- a/crates/zng-app/src/view_process.rs
+++ b/crates/zng-app/src/view_process.rs
@@ -31,7 +31,7 @@ use zng_view_api::{
     ipc::{IpcBytes, IpcBytesReceiver},
     window::{
         CursorIcon, FocusIndicator, FrameRequest, FrameUpdateRequest, HeadlessOpenData, HeadlessRequest, MonitorInfo, RenderMode,
-        ResizeDirection, VideoMode, WindowRequest, WindowStateAll,
+        ResizeDirection, VideoMode, WindowButton, WindowRequest, WindowStateAll,
     },
     Event, ViewProcessGen, ViewProcessOffline,
 };
@@ -751,6 +751,11 @@ impl ViewWindow {
         self.0.call(|id, p| p.set_video_mode(id, mode))
     }
 
+    /// Set enabled window chrome buttons.
+    pub fn set_enabled_buttons(&self, buttons: WindowButton) -> Result<()> {
+        self.0.call(|id, p| p.set_enabled_buttons(id, buttons))
+    }
+
     /// Reference the window renderer.
     pub fn renderer(&self) -> ViewRenderer {
         ViewRenderer(Arc::downgrade(&self.0))
diff --git a/crates/zng-ext-window/src/control.rs b/crates/zng-ext-window/src/control.rs
index 6b88bf108..d20f5e02d 100644
--- a/crates/zng-ext-window/src/control.rs
+++ b/crates/zng-ext-window/src/control.rs
@@ -301,6 +301,12 @@ impl HeadedCtrl {
                 });
             }
 
+            if let Some(buttons) = self.vars.enabled_buttons().get_new() {
+                self.update_gen(move |view| {
+                    let _: Ignore = view.set_enabled_buttons(buttons);
+                })
+            }
+
             if prev_state != new_state {
                 self.update_gen(move |view| {
                     let _: Ignore = view.set_state(new_state);
@@ -1094,6 +1100,7 @@ impl HeadedCtrl {
             always_on_top: self.vars.always_on_top().get(),
             movable: self.vars.movable().get(),
             resizable: self.vars.resizable().get(),
+            enabled_buttons: self.vars.enabled_buttons().get(),
             icon: self
                 .img_res
                 .icon_var
@@ -1219,6 +1226,7 @@ impl HeadedCtrl {
             always_on_top: self.vars.always_on_top().get(),
             movable: self.vars.movable().get(),
             resizable: self.vars.resizable().get(),
+            enabled_buttons: self.vars.enabled_buttons().get(),
             icon: self
                 .img_res
                 .icon_var
diff --git a/crates/zng-ext-window/src/types.rs b/crates/zng-ext-window/src/types.rs
index b2b0a9ff1..903ca2993 100644
--- a/crates/zng-ext-window/src/types.rs
+++ b/crates/zng-ext-window/src/types.rs
@@ -22,7 +22,7 @@ use zng_view_api::{
     window::{EventCause, FrameId},
 };
 
-pub use zng_view_api::window::{FocusIndicator, RenderMode, VideoMode, WindowState};
+pub use zng_view_api::window::{FocusIndicator, RenderMode, VideoMode, WindowButton, WindowState};
 
 use crate::{HeadlessMonitor, WINDOW_Ext as _};
 
diff --git a/crates/zng-ext-window/src/vars.rs b/crates/zng-ext-window/src/vars.rs
index ade922a0d..624e5d123 100644
--- a/crates/zng-ext-window/src/vars.rs
+++ b/crates/zng-ext-window/src/vars.rs
@@ -12,7 +12,7 @@ use zng_unique_id::IdSet;
 use zng_var::{merge_var, var, ArcVar, BoxedVar, ReadOnlyArcVar, Var};
 use zng_view_api::{
     config::ColorScheme,
-    window::{CursorIcon, FocusIndicator, RenderMode, VideoMode, WindowState},
+    window::{CursorIcon, FocusIndicator, RenderMode, VideoMode, WindowButton, WindowState},
 };
 
 use crate::{AutoSize, CursorImg, FrameCaptureMode, MonitorQuery, WindowIcon};
@@ -51,6 +51,8 @@ pub(super) struct WindowVarsData {
     pub(super) restore_state: ArcVar<WindowState>,
     pub(super) restore_rect: ArcVar<DipRect>,
 
+    enabled_buttons: ArcVar<WindowButton>,
+
     resizable: ArcVar<bool>,
     movable: ArcVar<bool>,
 
@@ -119,6 +121,8 @@ impl WindowVars {
                 DipSize::new(Dip::new(800), Dip::new(600)),
             )),
 
+            enabled_buttons: var(WindowButton::all()),
+
             min_size: var(Size::new(192, 48)),
             max_size: var(Size::new(100.pct(), 100.pct())),
 
@@ -514,6 +518,11 @@ impl WindowVars {
         self.0.movable.clone()
     }
 
+    /// Defines the enabled state of the window chrome buttons.
+    pub fn enabled_buttons(&self) -> ArcVar<WindowButton> {
+        self.0.enabled_buttons.clone()
+    }
+
     /// Defines if the window should always stay on top of other windows.
     ///
     /// Note this only applies to other windows that are not also "always-on-top".
diff --git a/crates/zng-view-api/src/lib.rs b/crates/zng-view-api/src/lib.rs
index 559840751..ab492eb79 100644
--- a/crates/zng-view-api/src/lib.rs
+++ b/crates/zng-view-api/src/lib.rs
@@ -331,6 +331,9 @@ declare_api! {
     /// if canceled by setting to `None`.
     pub fn set_focus_indicator(&mut self, id: WindowId, indicator: Option<window::FocusIndicator>);
 
+    /// Set enabled window chrome buttons.
+    pub fn set_enabled_buttons(&mut self, id: WindowId, buttons: window::WindowButton);
+
     /// Brings the window to the front and sets input focus.
     ///
     /// Sends an [`Event::FocusChanged`] if the window is focused, the request can be ignored by the window manager, or if the
diff --git a/crates/zng-view-api/src/window.rs b/crates/zng-view-api/src/window.rs
index 680a91305..1cc9b402b 100644
--- a/crates/zng-view-api/src/window.rs
+++ b/crates/zng-view-api/src/window.rs
@@ -411,6 +411,9 @@ pub struct WindowRequest {
 
     /// IME cursor area, if IME is enabled.
     pub ime_area: Option<DipRect>,
+
+    /// Enabled window chrome buttons.
+    pub enabled_buttons: WindowButton,
 }
 impl WindowRequest {
     /// Corrects invalid values if [`kiosk`] is `true`.
@@ -996,3 +999,16 @@ pub enum EventCause {
     /// App affected the window.
     App,
 }
+
+bitflags::bitflags! {
+    /// Window chrome buttons.
+    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+    pub struct WindowButton: u32 {
+        /// Close button.
+        const CLOSE  = 1 << 0;
+        /// Minimize button.
+        const MINIMIZE  = 1 << 1;
+        /// Maximize/restore button.
+        const MAXIMIZE  = 1 << 2;
+    }
+}
diff --git a/crates/zng-view/src/lib.rs b/crates/zng-view/src/lib.rs
index 23e157893..e87013d6b 100644
--- a/crates/zng-view/src/lib.rs
+++ b/crates/zng-view/src/lib.rs
@@ -1634,6 +1634,10 @@ impl Api for App {
         self.with_window(id, |w| w.drag_resize(direction), || ())
     }
 
+    fn set_enabled_buttons(&mut self, id: WindowId, buttons: zng_view_api::window::WindowButton) {
+        self.with_window(id, |w| w.set_enabled_buttons(buttons), || ())
+    }
+
     fn open_title_bar_context_menu(&mut self, id: WindowId, position: DipPoint) {
         self.with_window(id, |w| w.open_title_bar_context_menu(position), || ())
     }
diff --git a/crates/zng-view/src/util.rs b/crates/zng-view/src/util.rs
index 230e87b39..f22a46322 100644
--- a/crates/zng-view/src/util.rs
+++ b/crates/zng-view/src/util.rs
@@ -12,7 +12,7 @@ use zng_unit::*;
 use zng_view_api::access::AccessNodeId;
 use zng_view_api::clipboard as clipboard_api;
 use zng_view_api::keyboard::NativeKeyCode;
-use zng_view_api::window::{FrameCapture, FrameRequest, FrameUpdateRequest, ResizeDirection};
+use zng_view_api::window::{FrameCapture, FrameRequest, FrameUpdateRequest, ResizeDirection, WindowButton};
 use zng_view_api::{
     config::ColorScheme,
     keyboard::{Key, KeyCode, KeyState},
@@ -226,6 +226,20 @@ impl ResizeDirectionToWinit for ResizeDirection {
     }
 }
 
+pub trait WindowButtonsToWinit {
+    fn to_winit(self) -> winit::window::WindowButtons;
+}
+impl WindowButtonsToWinit for WindowButton {
+    fn to_winit(self) -> winit::window::WindowButtons {
+        let mut r = winit::window::WindowButtons::empty();
+        r.set(winit::window::WindowButtons::CLOSE, self.contains(WindowButton::CLOSE));
+        r.set(winit::window::WindowButtons::MINIMIZE, self.contains(WindowButton::MINIMIZE));
+        r.set(winit::window::WindowButtons::MAXIMIZE, self.contains(WindowButton::MAXIMIZE));
+
+        r
+    }
+}
+
 pub trait CursorToWinit {
     fn to_winit(self) -> winit::window::CursorIcon;
 }
diff --git a/crates/zng-view/src/window.rs b/crates/zng-view/src/window.rs
index 71ed22a05..120c70964 100644
--- a/crates/zng-view/src/window.rs
+++ b/crates/zng-view/src/window.rs
@@ -30,7 +30,7 @@ use zng_view_api::{
     image::{ImageId, ImageLoadedData, ImageMaskMode, ImageTextureId},
     window::{
         CursorIcon, FocusIndicator, FrameCapture, FrameId, FrameRequest, FrameUpdateRequest, RenderMode, ResizeDirection, VideoMode,
-        WindowId, WindowRequest, WindowState, WindowStateAll,
+        WindowButton, WindowId, WindowRequest, WindowState, WindowStateAll,
     },
     DeviceId, Event, ViewProcessGen,
 };
@@ -50,8 +50,8 @@ use crate::{
     image_cache::{Image, ImageCache, ImageUseMap, WrImageCache},
     px_wr::PxToWr as _,
     util::{
-        frame_render_reasons, frame_update_render_reasons, CursorToWinit, DipToWinit, PxToWinit, ResizeDirectionToWinit as _, WinitToDip,
-        WinitToPx,
+        frame_render_reasons, frame_update_render_reasons, CursorToWinit, DipToWinit, PxToWinit, ResizeDirectionToWinit as _,
+        WindowButtonsToWinit as _, WinitToDip, WinitToPx,
     },
     AppEvent, AppEventSender, FrameReadyMsg, WrNotifier,
 };
@@ -416,6 +416,8 @@ impl Window {
         win.set_cursor(cfg.cursor);
         win.set_taskbar_visible(cfg.taskbar_visible);
 
+        win.set_enabled_buttons(cfg.enabled_buttons);
+
         if win.ime_area.is_some() {
             win.window.set_ime_allowed(true);
         }
@@ -1115,6 +1117,11 @@ impl Window {
         }
     }
 
+    /// Set enabled chrome buttons.
+    pub fn set_enabled_buttons(&self, buttons: WindowButton) {
+        self.window.set_enabled_buttons(buttons.to_winit());
+    }
+
     /// Open windows title bar context menu.
     pub fn open_title_bar_context_menu(&self, pos: DipPoint) {
         self.window.show_window_menu(pos.to_winit())
diff --git a/crates/zng-wgt-window/src/window_properties.rs b/crates/zng-wgt-window/src/window_properties.rs
index 6cbc47664..b244d27cf 100644
--- a/crates/zng-wgt-window/src/window_properties.rs
+++ b/crates/zng-wgt-window/src/window_properties.rs
@@ -2,8 +2,8 @@ use std::time::Duration;
 
 use zng_ext_config::{AnyConfig as _, ConfigKey, ConfigStatus, CONFIG};
 use zng_ext_window::{
-    AutoSize, FrameCaptureMode, MonitorQuery, WINDOW_Ext as _, WindowIcon, WindowLoadingHandle, WindowState, WindowVars, MONITORS,
-    WINDOW_LOAD_EVENT,
+    AutoSize, FrameCaptureMode, MonitorQuery, WINDOW_Ext as _, WindowButton, WindowIcon, WindowLoadingHandle, WindowState, WindowVars,
+    MONITORS, WINDOW_LOAD_EVENT,
 };
 use zng_wgt::prelude::*;
 
@@ -92,6 +92,8 @@ set_properties! {
     color_scheme: Option<ColorScheme>,
 
     frame_capture_mode: FrameCaptureMode,
+
+    enabled_buttons: WindowButton,
 }
 
 macro_rules! map_properties {
diff --git a/crates/zng/src/window.rs b/crates/zng/src/window.rs
index d9a7311c8..6fa5d668c 100644
--- a/crates/zng/src/window.rs
+++ b/crates/zng/src/window.rs
@@ -107,8 +107,8 @@ pub use zng_app::window::WINDOW as WINDOW_;
 pub use zng_ext_window::{
     AppRunWindowExt, AutoSize, CloseWindowResult, FocusIndicator, FrameCaptureMode, FrameImageReadyArgs, HeadlessAppWindowExt,
     HeadlessMonitor, ImeArgs, MonitorInfo, MonitorQuery, MonitorsChangedArgs, ParallelWin, RenderMode, StartPosition, VideoMode,
-    WINDOW_Ext, WidgetInfoBuilderImeArea, WidgetInfoImeArea, WindowChangedArgs, WindowCloseArgs, WindowCloseRequestedArgs, WindowIcon,
-    WindowLoadingHandle, WindowOpenArgs, WindowRoot, WindowRootExtenderArgs, WindowState, WindowStateAllowed, WindowVars,
+    WINDOW_Ext, WidgetInfoBuilderImeArea, WidgetInfoImeArea, WindowButton, WindowChangedArgs, WindowCloseArgs, WindowCloseRequestedArgs,
+    WindowIcon, WindowLoadingHandle, WindowOpenArgs, WindowRoot, WindowRootExtenderArgs, WindowState, WindowStateAllowed, WindowVars,
     FRAME_IMAGE_READY_EVENT, IME_EVENT, MONITORS, MONITORS_CHANGED_EVENT, WINDOWS, WINDOW_CHANGED_EVENT, WINDOW_CLOSE_EVENT,
     WINDOW_CLOSE_REQUESTED_EVENT, WINDOW_LOAD_EVENT, WINDOW_OPEN_EVENT,
 };
diff --git a/examples/calculator.rs b/examples/calculator.rs
index d09f9fdf8..fb43e2f4e 100644
--- a/examples/calculator.rs
+++ b/examples/calculator.rs
@@ -31,6 +31,7 @@ fn app_main() {
             data = var(Calculator::default());
             resizable = false;
             auto_size = true;
+            enabled_buttons = !window::WindowButton::MAXIMIZE;
             padding = 5;
             child = Stack! {
                 direction = StackDirection::top_to_bottom();
diff --git a/examples/countdown.rs b/examples/countdown.rs
index 02b3c6c21..81a75535b 100644
--- a/examples/countdown.rs
+++ b/examples/countdown.rs
@@ -3,15 +3,15 @@
 #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
 use zng::{image, prelude::*, widget::background_color};
 
-use zng::view_process::prebuilt as view_process;
+use zng::view_process::default as view_process;
 
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    // view_process::init();
     zng::app::crash_handler::init_debug();
 
-    // view_process::run_same_process(app_main);
-    app_main();
+    view_process::run_same_process(app_main);
+    // app_main();
 }
 
 fn app_main() {
@@ -34,6 +34,7 @@ fn app_main() {
             size = (280, 120);
             start_position = window::StartPosition::CenterMonitor;
             resizable = false;
+            enabled_buttons = !window::WindowButton::MAXIMIZE;
 
             color_scheme = color::ColorScheme::Dark;
 
diff --git a/examples/cursor.rs b/examples/cursor.rs
index 60131e6ab..6619b484b 100644
--- a/examples/cursor.rs
+++ b/examples/cursor.rs
@@ -34,6 +34,7 @@ fn app_main() {
         Window! {
             title = "Cursor Example";
             resizable = false;
+            enabled_buttons = !window::WindowButton::MAXIMIZE;
             auto_size = true;
             padding = 20;
             child = Stack!(top_to_bottom, ui_vec![
diff --git a/examples/respawn.rs b/examples/respawn.rs
index fb874876b..e7a532730 100644
--- a/examples/respawn.rs
+++ b/examples/respawn.rs
@@ -94,6 +94,7 @@ fn app_crash_dialog(args: zng::app::crash_handler::CrashArgs) -> ! {
             start_position = zng::window::StartPosition::CenterMonitor;
             auto_size = window::AutoSize::CONTENT;
             min_size = (300, 100);
+            enabled_buttons = !window::WindowButton::MAXIMIZE;
 
             on_load = hn_once!(|_| {
                 // force to foreground
diff --git a/examples/shortcut.rs b/examples/shortcut.rs
index 0b9e963c4..c55c82bce 100644
--- a/examples/shortcut.rs
+++ b/examples/shortcut.rs
@@ -67,6 +67,7 @@ fn app_main() {
             title = "Shortcut Example";
             auto_size = true;
             resizable = false;
+            enabled_buttons = !window::WindowButton::MAXIMIZE;
             auto_size_origin = layout::Point::center();
             padding = 50;
             start_position = window::StartPosition::CenterMonitor;


Test patch: