소스 검색

feat: add granular size constraints APIs (#10242)

Amr Bashir 1 년 전
부모
커밋
da25f73530

+ 7 - 0
.changes/split-min-max-constraints-apis-runtime-js.md

@@ -0,0 +1,7 @@
+---
+"@tauri-apps/api": patch:feat
+---
+
+Add APIs to enable setting window size constraints separately:
+- Added `WindowSizeConstraints` interface in `window` and `webviewWindow` modules.
+- Added `Window.setSizeConstraints` and `WebviewWindow.setSizeConstraints`

+ 8 - 0
.changes/split-min-max-constraints-apis-runtime.md

@@ -0,0 +1,8 @@
+---
+"tauri": patch:feat
+---
+
+Add APIs to enable setting window size constraints separately:
+- Added `WindowBuilder::inner_size_constraints` and `WebviewWindowBuilder::inner_size_constraints` which can be used for setting granular constraints.
+- Added `WindowSizeConstraints` struct
+- Added `Window::set_size_constraints` and `WebviewWindow::set_size_constraints`

+ 6 - 0
.changes/split-min-max-constraints-apis.md

@@ -0,0 +1,6 @@
+---
+"tauri-runtime": "patch"
+"tauri-runtime-wry": "patch"
+---
+
+Add `inner_size_constraints` method on `WindowBuilder` trait and `set_size_constraints` method on `WindowDispatch` trait.

+ 6 - 0
.changes/split-min-max-constraints.md

@@ -0,0 +1,6 @@
+---
+"tauri": "patch:bug"
+"@tauri-apps/api": "patch:bug"
+---
+
+Apply `minWidth`, `minHieght`, `maxWidth` and `maxHeight` constraints separately, which fixes a long standing bug where these constraints were never applied unless width and height were constrained together.

+ 46 - 7
core/tauri-runtime-wry/src/lib.rs

@@ -20,7 +20,7 @@ use tauri_runtime::{
   webview::{DetachedWebview, DownloadEvent, PendingWebview, WebviewIpcHandler},
   window::{
     CursorIcon, DetachedWindow, DragDropEvent, PendingWindow, RawWindow, WebviewEvent,
-    WindowBuilder, WindowBuilderBase, WindowEvent, WindowId,
+    WindowBuilder, WindowBuilderBase, WindowEvent, WindowId, WindowSizeConstraints,
   },
   DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon, ProgressBarState,
   ProgressBarStatus, Result, RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType,
@@ -43,8 +43,8 @@ use wry::WebViewBuilderExtWindows;
 use tao::{
   dpi::{
     LogicalPosition as TaoLogicalPosition, LogicalSize as TaoLogicalSize,
-    PhysicalPosition as TaoPhysicalPosition, PhysicalSize as TaoPhysicalSize,
-    Position as TaoPosition, Size as TaoSize,
+    LogicalUnit as ToaLogicalUnit, PhysicalPosition as TaoPhysicalPosition,
+    PhysicalSize as TaoPhysicalSize, Position as TaoPosition, Size as TaoSize,
   },
   event::{Event, StartCause, WindowEvent as TaoWindowEvent},
   event_loop::{
@@ -774,12 +774,22 @@ impl WindowBuilder for WindowBuilderWrapper {
         .minimizable(config.minimizable)
         .shadow(config.shadow);
 
-      if let (Some(min_width), Some(min_height)) = (config.min_width, config.min_height) {
-        window = window.min_inner_size(min_width, min_height);
+      let mut constraints = WindowSizeConstraints::default();
+
+      if let Some(min_width) = config.min_width {
+        constraints.min_width = Some(ToaLogicalUnit::new(min_width).into());
+      }
+      if let Some(min_height) = config.min_height {
+        constraints.min_height = Some(ToaLogicalUnit::new(min_height).into());
+      }
+      if let Some(max_width) = config.max_width {
+        constraints.max_width = Some(ToaLogicalUnit::new(max_width).into());
       }
-      if let (Some(max_width), Some(max_height)) = (config.max_width, config.max_height) {
-        window = window.max_inner_size(max_width, max_height);
+      if let Some(max_height) = config.max_height {
+        constraints.max_height = Some(ToaLogicalUnit::new(max_height).into());
       }
+      window = window.inner_size_constraints(constraints);
+
       if let (Some(x), Some(y)) = (config.x, config.y) {
         window = window.position(x, y);
       }
@@ -823,6 +833,16 @@ impl WindowBuilder for WindowBuilderWrapper {
     self
   }
 
+  fn inner_size_constraints(mut self, constraints: WindowSizeConstraints) -> Self {
+    self.inner.window.inner_size_constraints = tao::window::WindowSizeConstraints {
+      min_width: constraints.min_width,
+      min_height: constraints.min_height,
+      max_width: constraints.max_width,
+      max_height: constraints.max_height,
+    };
+    self
+  }
+
   fn resizable(mut self, resizable: bool) -> Self {
     self.inner = self.inner.with_resizable(resizable);
     self
@@ -1144,6 +1164,7 @@ pub enum WindowMessage {
   SetSize(Size),
   SetMinSize(Option<Size>),
   SetMaxSize(Option<Size>),
+  SetSizeConstraints(WindowSizeConstraints),
   SetPosition(Position),
   SetFullscreen(bool),
   SetFocus,
@@ -1850,6 +1871,16 @@ impl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {
     )
   }
 
+  fn set_size_constraints(&self, constraints: WindowSizeConstraints) -> Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Window(
+        self.window_id,
+        WindowMessage::SetSizeConstraints(constraints),
+      ),
+    )
+  }
+
   fn set_position(&self, position: Position) -> Result<()> {
     send_user_message(
       &self.context,
@@ -2831,6 +2862,14 @@ fn handle_user_message<T: UserEvent>(
           WindowMessage::SetMaxSize(size) => {
             window.set_max_inner_size(size.map(|s| SizeWrapper::from(s).0));
           }
+          WindowMessage::SetSizeConstraints(constraints) => {
+            window.set_inner_size_constraints(tao::window::WindowSizeConstraints {
+              min_width: constraints.min_width,
+              min_height: constraints.min_height,
+              max_width: constraints.max_width,
+              max_height: constraints.max_height,
+            });
+          }
           WindowMessage::SetPosition(position) => {
             window.set_outer_position(PositionWrapper::from(position).0)
           }

+ 7 - 1
core/tauri-runtime/src/lib.rs

@@ -26,7 +26,10 @@ pub mod window;
 
 use dpi::{PhysicalPosition, PhysicalSize, Position, Size};
 use monitor::Monitor;
-use window::{CursorIcon, DetachedWindow, PendingWindow, RawWindow, WebviewEvent, WindowEvent};
+use window::{
+  CursorIcon, DetachedWindow, PendingWindow, RawWindow, WebviewEvent, WindowEvent,
+  WindowSizeConstraints,
+};
 use window::{WindowBuilder, WindowId};
 
 use http::{
@@ -735,6 +738,9 @@ pub trait WindowDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 's
   /// Updates the window max inner size.
   fn set_max_size(&self, size: Option<Size>) -> Result<()>;
 
+  /// Sets this window's minimum inner width.
+  fn set_size_constraints(&self, constraints: WindowSizeConstraints) -> Result<()>;
+
   /// Updates the window position.
   fn set_position(&self, position: Position) -> Result<()>;
 

+ 28 - 1
core/tauri-runtime/src/window.rs

@@ -9,7 +9,8 @@ use crate::{
   Icon, Runtime, UserEvent, WindowDispatch,
 };
 
-use serde::{Deserialize, Deserializer};
+use dpi::PixelUnit;
+use serde::{Deserialize, Deserializer, Serialize};
 use tauri_utils::{config::WindowConfig, Theme};
 #[cfg(windows)]
 use windows::Win32::Foundation::HWND;
@@ -201,6 +202,28 @@ impl<'de> Deserialize<'de> for CursorIcon {
   }
 }
 
+/// Window size constraints
+#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct WindowSizeConstraints {
+  /// The minimum width a window can be, If this is `None`, the window will have no minimum width.
+  ///
+  /// The default is `None`.
+  pub min_width: Option<PixelUnit>,
+  /// The minimum height a window can be, If this is `None`, the window will have no minimum height.
+  ///
+  /// The default is `None`.
+  pub min_height: Option<PixelUnit>,
+  /// The maximum width a window can be, If this is `None`, the window will have no maximum width.
+  ///
+  /// The default is `None`.
+  pub max_width: Option<PixelUnit>,
+  /// The maximum height a window can be, If this is `None`, the window will have no maximum height.
+  ///
+  /// The default is `None`.
+  pub max_height: Option<PixelUnit>,
+}
+
 /// Do **NOT** implement this trait except for use in a custom [`Runtime`]
 ///
 /// This trait is separate from [`WindowBuilder`] to prevent "accidental" implementation.
@@ -237,6 +260,10 @@ pub trait WindowBuilder: WindowBuilderBase {
   #[must_use]
   fn max_inner_size(self, max_width: f64, max_height: f64) -> Self;
 
+  /// Window inner size constraints.
+  #[must_use]
+  fn inner_size_constraints(self, constraints: WindowSizeConstraints) -> Self;
+
   /// Whether the window is resizable or not.
   /// When resizable is set to false, native window's maximize button is automatically disabled.
   #[must_use]

+ 1 - 0
core/tauri/build.rs

@@ -92,6 +92,7 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
       ("set_content_protected", false),
       ("set_size", false),
       ("set_min_size", false),
+      ("set_size_constraints", false),
       ("set_max_size", false),
       ("set_position", false),
       ("set_fullscreen", false),

+ 26 - 0
core/tauri/permissions/window/autogenerated/reference.md

@@ -1390,6 +1390,32 @@ Denies the set_size command without any pre-configured scope.
 <tr>
 <td>
 
+`window:allow-set-size-constraints`
+
+</td>
+<td>
+
+Enables the set_size_constraints command without any pre-configured scope.
+
+</td>
+</tr>
+
+<tr>
+<td>
+
+`window:deny-set-size-constraints`
+
+</td>
+<td>
+
+Denies the set_size_constraints command without any pre-configured scope.
+
+</td>
+</tr>
+
+<tr>
+<td>
+
 `window:allow-set-skip-taskbar`
 
 </td>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
core/tauri/scripts/bundle.global.js


+ 1 - 1
core/tauri/src/lib.rs

@@ -222,7 +222,7 @@ pub use {
   self::runtime::{
     dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size},
     webview::WebviewAttributes,
-    window::{CursorIcon, DragDropEvent},
+    window::{CursorIcon, DragDropEvent, WindowSizeConstraints},
     DeviceEventFilter, Rect, UserAttentionType,
   },
   self::state::{State, StateManager},

+ 14 - 0
core/tauri/src/test/mock_runtime.rs

@@ -332,6 +332,13 @@ impl WindowBuilder for MockWindowBuilder {
     self
   }
 
+  fn inner_size_constraints(
+    self,
+    constraints: tauri_runtime::window::WindowSizeConstraints,
+  ) -> Self {
+    self
+  }
+
   fn resizable(self, resizable: bool) -> Self {
     self
   }
@@ -937,6 +944,13 @@ impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
   fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()> {
     Ok(())
   }
+
+  fn set_size_constraints(
+    &self,
+    constraints: tauri_runtime::window::WindowSizeConstraints,
+  ) -> Result<()> {
+    Ok(())
+  }
 }
 
 #[derive(Debug, Clone)]

+ 13 - 0
core/tauri/src/webview/webview_window.rs

@@ -27,6 +27,7 @@ use crate::{
   },
 };
 use serde::Serialize;
+use tauri_runtime::window::WindowSizeConstraints;
 use tauri_utils::config::{WebviewUrl, WindowConfig};
 use url::Url;
 
@@ -406,6 +407,13 @@ impl<'a, R: Runtime, M: Manager<R>> WebviewWindowBuilder<'a, R, M> {
     self
   }
 
+  /// Window inner size constraints.
+  #[must_use]
+  pub fn inner_size_constraints(mut self, constraints: WindowSizeConstraints) -> Self {
+    self.window_builder = self.window_builder.inner_size_constraints(constraints);
+    self
+  }
+
   /// Whether the window is resizable or not.
   /// When resizable is set to false, native window's maximize button is automatically disabled.
   #[must_use]
@@ -1469,6 +1477,11 @@ impl<R: Runtime> WebviewWindow<R> {
     self.webview.window().set_max_size(size.map(|s| s.into()))
   }
 
+  /// Sets this window's minimum inner width.
+  pub fn set_size_constraints(&self, constriants: WindowSizeConstraints) -> crate::Result<()> {
+    self.webview.window().set_size_constraints(constriants)
+  }
+
   /// Sets this window's position.
   pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
     self.webview.window().set_position(position)

+ 17 - 0
core/tauri/src/window/mod.rs

@@ -9,6 +9,7 @@ pub(crate) mod plugin;
 use tauri_runtime::{
   dpi::{PhysicalPosition, PhysicalSize},
   webview::PendingWebview,
+  window::WindowSizeConstraints,
 };
 pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState};
 
@@ -492,6 +493,13 @@ impl<'a, R: Runtime, M: Manager<R>> WindowBuilder<'a, R, M> {
     self
   }
 
+  /// Window inner size constraints.
+  #[must_use]
+  pub fn inner_size_constraints(mut self, constraints: WindowSizeConstraints) -> Self {
+    self.window_builder = self.window_builder.inner_size_constraints(constraints);
+    self
+  }
+
   /// Whether the window is resizable or not.
   /// When resizable is set to false, native window's maximize button is automatically disabled.
   #[must_use]
@@ -1855,6 +1863,15 @@ tauri::Builder::default()
       .map_err(Into::into)
   }
 
+  /// Sets this window's minimum inner width.
+  pub fn set_size_constraints(&self, constriants: WindowSizeConstraints) -> crate::Result<()> {
+    self
+      .window
+      .dispatcher
+      .set_size_constraints(constriants)
+      .map_err(Into::into)
+  }
+
   /// Sets this window's position.
   pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
     self

+ 3 - 1
core/tauri/src/window/plugin.rs

@@ -11,7 +11,7 @@ use crate::{
 
 #[cfg(desktop)]
 mod desktop_commands {
-  use tauri_runtime::ResizeDirection;
+  use tauri_runtime::{window::WindowSizeConstraints, ResizeDirection};
   use tauri_utils::TitleBarStyle;
 
   use super::*;
@@ -132,6 +132,7 @@ mod desktop_commands {
   setter!(set_progress_bar, ProgressBarState);
   setter!(set_visible_on_all_workspaces, bool);
   setter!(set_title_bar_style, TitleBarStyle);
+  setter!(set_size_constraints, WindowSizeConstraints);
 
   #[command(root = "crate")]
   pub async fn set_icon<R: Runtime>(
@@ -264,6 +265,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
             desktop_commands::set_size,
             desktop_commands::set_min_size,
             desktop_commands::set_max_size,
+            desktop_commands::set_size_constraints,
             desktop_commands::set_position,
             desktop_commands::set_fullscreen,
             desktop_commands::set_focus,

+ 36 - 0
tooling/api/src/window.ts

@@ -183,6 +183,13 @@ export enum ProgressBarStatus {
   Error = 'error'
 }
 
+export interface WindowSizeConstraints {
+  minWidth?: number
+  minHeight?: number
+  maxWidth?: number
+  maxHeight?: number
+}
+
 export interface ProgressBarState {
   /**
    * The progress bar status.
@@ -1311,6 +1318,35 @@ class Window {
     })
   }
 
+  /**
+   * Sets the window inner size constraints.
+   * @example
+   * ```typescript
+   * import { getCurrentWindow } from '@tauri-apps/api/window';
+   * await getCurrentWindow().setSizeConstraints({ minWidth: 300 });
+   * ```
+   *
+   * @param size The logical or physical inner size, or `null` to unset the constraint.
+   * @returns A promise indicating the success or failure of the operation.
+   */
+  async setSizeConstraints(
+    constraints: WindowSizeConstraints | null | undefined
+  ): Promise<void> {
+    function logical(pixel?: number): { Logical: number } | null {
+      return pixel ? { Logical: pixel } : null
+    }
+
+    return invoke('plugin:window|set_size_constraints', {
+      label: this.label,
+      value: {
+        minWidth: logical(constraints?.minWidth),
+        minHeight: logical(constraints?.minHeight),
+        maxWidth: logical(constraints?.maxWidth),
+        maxHeight: logical(constraints?.maxHeight)
+      }
+    })
+  }
+
   /**
    * Sets the window outer position.
    * @example

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.