瀏覽代碼

feat: set application progress bar, close #7999 (#8009)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Jason Tsai 1 年之前
父節點
當前提交
c085addab5

+ 8 - 0
.changes/add-progress-bar.md

@@ -0,0 +1,8 @@
+---
+"tauri": 'patch:feat'
+"tauri-runtime": 'patch:feat'
+"tauri-runtime-wry": 'patch:feat'
+"tauri-utils": 'patch:feat'
+---
+
+Added `set_progress_bar` to `Window`.

+ 5 - 0
.changes/set-progress-bar-api.md

@@ -0,0 +1,5 @@
+---
+"@tauri-apps/api": patch:feat
+---
+
+Added the `setProgressBar` API on the `Window` class.

+ 49 - 3
core/tauri-runtime-wry/src/lib.rs

@@ -41,7 +41,9 @@ use wry::webview::WebViewBuilderExtWindows;
 
 #[cfg(target_os = "macos")]
 use tauri_utils::TitleBarStyle;
-use tauri_utils::{config::WindowConfig, debug_eprintln, Theme};
+use tauri_utils::{
+  config::WindowConfig, debug_eprintln, ProgressBarState, ProgressBarStatus, Theme,
+};
 use wry::{
   application::{
     dpi::{
@@ -56,8 +58,9 @@ use wry::{
     },
     monitor::MonitorHandle,
     window::{
-      CursorIcon as WryCursorIcon, Fullscreen, Icon as WryWindowIcon, Theme as WryTheme,
-      UserAttentionType as WryUserAttentionType,
+      CursorIcon as WryCursorIcon, Fullscreen, Icon as WryWindowIcon,
+      ProgressBarState as WryProgressBarState, ProgressState as WryProgressState,
+      Theme as WryTheme, UserAttentionType as WryUserAttentionType,
     },
   },
   webview::{FileDropEvent as WryFileDropEvent, Url, WebContext, WebView, WebViewBuilder},
@@ -520,6 +523,35 @@ impl From<CursorIcon> for CursorIconWrapper {
   }
 }
 
+pub struct ProgressStateWrapper(pub WryProgressState);
+
+impl From<ProgressBarStatus> for ProgressStateWrapper {
+  fn from(status: ProgressBarStatus) -> Self {
+    let state = match status {
+      ProgressBarStatus::None => WryProgressState::None,
+      ProgressBarStatus::Normal => WryProgressState::Normal,
+      ProgressBarStatus::Indeterminate => WryProgressState::Indeterminate,
+      ProgressBarStatus::Paused => WryProgressState::Paused,
+      ProgressBarStatus::Error => WryProgressState::Error,
+    };
+    Self(state)
+  }
+}
+
+pub struct ProgressBarStateWrapper(pub WryProgressBarState);
+
+impl From<ProgressBarState> for ProgressBarStateWrapper {
+  fn from(progress_state: ProgressBarState) -> Self {
+    Self(WryProgressBarState {
+      progress: progress_state.progress,
+      state: progress_state
+        .status
+        .map(|state| ProgressStateWrapper::from(state).0),
+      unity_uri: progress_state.unity_uri,
+    })
+  }
+}
+
 #[derive(Clone, Default)]
 pub struct WindowBuilderWrapper {
   inner: WryWindowBuilder,
@@ -1006,6 +1038,7 @@ pub enum WindowMessage {
   SetCursorIcon(CursorIcon),
   SetCursorPosition(Position),
   SetIgnoreCursorEvents(bool),
+  SetProgressBar(ProgressBarState),
   DragWindow,
   RequestRedraw,
 }
@@ -1520,6 +1553,16 @@ impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
       ),
     )
   }
+
+  fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Window(
+        self.window_id,
+        WindowMessage::SetProgressBar(progress_state),
+      ),
+    )
+  }
 }
 
 #[derive(Clone)]
@@ -2302,6 +2345,9 @@ fn handle_user_message<T: UserEvent>(
           WindowMessage::RequestRedraw => {
             window.request_redraw();
           }
+          WindowMessage::SetProgressBar(progress_state) => {
+            window.set_progress_bar(ProgressBarStateWrapper::from(progress_state).0);
+          }
         }
       }
     }

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

@@ -15,7 +15,7 @@
 use raw_window_handle::RawDisplayHandle;
 use serde::Deserialize;
 use std::{fmt::Debug, sync::mpsc::Sender};
-use tauri_utils::Theme;
+use tauri_utils::{ProgressBarState, Theme};
 use url::Url;
 
 /// Types useful for interacting with a user's monitors.
@@ -589,4 +589,12 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
 
   /// Executes javascript on the window this [`Dispatch`] represents.
   fn eval_script<S: Into<String>>(&self, script: S) -> Result<()>;
+
+  /// Sets the taskbar progress state.
+  ///
+  /// ## Platform-specific
+  ///
+  /// - **Linux / macOS**: Progress bar is app-wide and not specific to this window. Only supported desktop environments with `libunity` (e.g. GNOME).
+  /// - **iOS / Android:** Unsupported.
+  fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()>;
 }

+ 28 - 0
core/tauri-utils/src/lib.rs

@@ -416,3 +416,31 @@ pub fn display_path<P: AsRef<Path>>(p: P) -> String {
     .display()
     .to_string()
 }
+
+/// Progress bar status.
+#[derive(Debug, Clone, Copy, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub enum ProgressBarStatus {
+  /// Hide progress bar.
+  None,
+  /// Normal state.
+  Normal,
+  /// Indeterminate state. **Treated as Normal on Linux and macOS**
+  Indeterminate,
+  /// Paused state. **Treated as Normal on Linux**
+  Paused,
+  /// Error state. **Treated as Normal on Linux**
+  Error,
+}
+
+/// Progress Bar State
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ProgressBarState {
+  /// The progress bar status.
+  pub status: Option<ProgressBarStatus>,
+  /// The progress bar progress. This can be a value ranging from `0` to `100`
+  pub progress: Option<u64>,
+  /// The identifier for your app to communicate with the Unity desktop window manager **Linux Only**
+  pub unity_uri: Option<String>,
+}

File diff suppressed because it is too large
+ 0 - 0
core/tauri/scripts/bundle.global.js


+ 5 - 1
core/tauri/src/test/mock_runtime.rs

@@ -18,7 +18,7 @@ use tauri_runtime::{
 
 #[cfg(target_os = "macos")]
 use tauri_utils::TitleBarStyle;
-use tauri_utils::{config::WindowConfig, Theme};
+use tauri_utils::{config::WindowConfig, ProgressBarState, Theme};
 use url::Url;
 
 #[cfg(windows)]
@@ -676,6 +676,10 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
       .replace(script.into());
     Ok(())
   }
+
+  fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()> {
+    Ok(())
+  }
 }
 
 #[derive(Debug, Clone)]

+ 19 - 1
core/tauri/src/window/mod.rs

@@ -32,7 +32,10 @@ use crate::{
   },
   sealed::ManagerBase,
   sealed::RuntimeOrDispatch,
-  utils::config::{WindowConfig, WindowEffectsConfig, WindowUrl},
+  utils::{
+    config::{WindowConfig, WindowEffectsConfig, WindowUrl},
+    ProgressBarState,
+  },
   EventLoopMessage, Manager, Runtime, Theme, WindowEvent,
 };
 #[cfg(desktop)]
@@ -2044,6 +2047,21 @@ impl<R: Runtime> Window<R> {
   pub fn start_dragging(&self) -> crate::Result<()> {
     self.window.dispatcher.start_dragging().map_err(Into::into)
   }
+
+  /// Sets the taskbar progress state.
+  ///
+  /// ## Platform-specific
+  ///
+  /// - **Linux / macOS**: Progress bar is app-wide and not specific to this window.
+  /// - **Linux**: Only supported desktop environments with `libunity` (e.g. GNOME).
+  /// - **iOS / Android:** Unsupported.
+  pub fn set_progress_bar(&self, progress_state: ProgressBarState) -> crate::Result<()> {
+    self
+      .window
+      .dispatcher
+      .set_progress_bar(progress_state)
+      .map_err(Into::into)
+  }
 }
 
 /// Webview APIs.

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

@@ -12,6 +12,7 @@ use crate::{
 #[cfg(desktop)]
 mod desktop_commands {
   use serde::Deserialize;
+  use tauri_utils::ProgressBarState;
 
   use super::*;
   use crate::{
@@ -153,6 +154,7 @@ mod desktop_commands {
   setter!(set_cursor_position, Position);
   setter!(set_ignore_cursor_events, bool);
   setter!(start_dragging);
+  setter!(set_progress_bar, ProgressBarState);
   setter!(print);
 
   #[command(root = "crate")]
@@ -302,6 +304,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
             desktop_commands::set_cursor_position,
             desktop_commands::set_ignore_cursor_events,
             desktop_commands::start_dragging,
+            desktop_commands::set_progress_bar,
             desktop_commands::print,
             desktop_commands::set_icon,
             desktop_commands::toggle_maximize,

File diff suppressed because it is too large
+ 0 - 0
examples/api/dist/assets/index.js


+ 196 - 166
examples/api/src/views/Window.svelte

@@ -7,267 +7,281 @@
     PhysicalPosition,
     Effect,
     EffectState,
+    ProgressBarStatus,
     Window
-  } from "@tauri-apps/api/window";
-  import { invoke } from "@tauri-apps/api/primitives";
+  } from '@tauri-apps/api/window'
+  import { invoke } from '@tauri-apps/api/primitives'
 
-  const appWindow = getCurrent();
+  const appWindow = getCurrent()
 
-  let selectedWindow = appWindow.label;
+  let selectedWindow = appWindow.label
   const windowMap = {
-    [appWindow.label]: appWindow,
-  };
+    [appWindow.label]: appWindow
+  }
 
   const cursorIconOptions = [
-    "default",
-    "crosshair",
-    "hand",
-    "arrow",
-    "move",
-    "text",
-    "wait",
-    "help",
-    "progress",
+    'default',
+    'crosshair',
+    'hand',
+    'arrow',
+    'move',
+    'text',
+    'wait',
+    'help',
+    'progress',
     // something cannot be done
-    "notAllowed",
-    "contextMenu",
-    "cell",
-    "verticalText",
-    "alias",
-    "copy",
-    "noDrop",
+    'notAllowed',
+    'contextMenu',
+    'cell',
+    'verticalText',
+    'alias',
+    'copy',
+    'noDrop',
     // something can be grabbed
-    "grab",
+    'grab',
     /// something is grabbed
-    "grabbing",
-    "allScroll",
-    "zoomIn",
-    "zoomOut",
+    'grabbing',
+    'allScroll',
+    'zoomIn',
+    'zoomOut',
     // edge is to be moved
-    "eResize",
-    "nResize",
-    "neResize",
-    "nwResize",
-    "sResize",
-    "seResize",
-    "swResize",
-    "wResize",
-    "ewResize",
-    "nsResize",
-    "neswResize",
-    "nwseResize",
-    "colResize",
-    "rowResize",
-  ];
-
-  const windowsEffects = ["mica", "blur", "acrylic", "tabbed", "tabbedDark", "tabbedLight"];
-  const isWindows = navigator.appVersion.includes("Windows");
-  const isMacOS = navigator.appVersion.includes("Macintosh");
+    'eResize',
+    'nResize',
+    'neResize',
+    'nwResize',
+    'sResize',
+    'seResize',
+    'swResize',
+    'wResize',
+    'ewResize',
+    'nsResize',
+    'neswResize',
+    'nwseResize',
+    'colResize',
+    'rowResize'
+  ]
+
+  const windowsEffects = [
+    'mica',
+    'blur',
+    'acrylic',
+    'tabbed',
+    'tabbedDark',
+    'tabbedLight'
+  ]
+  const isWindows = navigator.appVersion.includes('Windows')
+  const isMacOS = navigator.appVersion.includes('Macintosh')
   let effectOptions = isWindows
     ? windowsEffects
     : Object.keys(Effect)
         .map((effect) => Effect[effect])
-        .filter((e) => !windowsEffects.includes(e));
+        .filter((e) => !windowsEffects.includes(e))
   const effectStateOptions = Object.keys(EffectState).map(
     (state) => EffectState[state]
-  );
-
-  export let onMessage;
-  const mainEl = document.querySelector("main");
-
-  let newWindowLabel;
-
-  let urlValue = "https://tauri.app";
-  let resizable = true;
-  let maximizable = true;
-  let minimizable = true;
-  let closable = true;
-  let maximized = false;
-  let decorations = true;
-  let alwaysOnTop = false;
-  let contentProtected = true;
-  let fullscreen = false;
-  let width = null;
-  let height = null;
-  let minWidth = null;
-  let minHeight = null;
-  let maxWidth = null;
-  let maxHeight = null;
-  let x = null;
-  let y = null;
-  let scaleFactor = 1;
-  let innerPosition = new PhysicalPosition(x, y);
-  let outerPosition = new PhysicalPosition(x, y);
-  let innerSize = new PhysicalSize(width, height);
-  let outerSize = new PhysicalSize(width, height);
-  let resizeEventUnlisten;
-  let moveEventUnlisten;
-  let cursorGrab = false;
-  let cursorVisible = true;
-  let cursorX = null;
-  let cursorY = null;
-  let cursorIcon = "default";
-  let cursorIgnoreEvents = false;
-  let windowTitle = "Awesome Tauri Example!";
-
-  let effects = [];
-  let selectedEffect;
-  let effectState;
-  let effectRadius;
-  let effectR, effectG, effectB, effectA;
-
-  let windowIconPath;
+  )
+
+  const progressBarStatusOptions = Object.keys(ProgressBarStatus).map(s => ProgressBarStatus[s])
+
+  export let onMessage
+  const mainEl = document.querySelector('main')
+
+  let newWindowLabel
+
+  let urlValue = 'https://tauri.app'
+  let resizable = true
+  let maximizable = true
+  let minimizable = true
+  let closable = true
+  let maximized = false
+  let decorations = true
+  let alwaysOnTop = false
+  let contentProtected = true
+  let fullscreen = false
+  let width = null
+  let height = null
+  let minWidth = null
+  let minHeight = null
+  let maxWidth = null
+  let maxHeight = null
+  let x = null
+  let y = null
+  let scaleFactor = 1
+  let innerPosition = new PhysicalPosition(x, y)
+  let outerPosition = new PhysicalPosition(x, y)
+  let innerSize = new PhysicalSize(width, height)
+  let outerSize = new PhysicalSize(width, height)
+  let resizeEventUnlisten
+  let moveEventUnlisten
+  let cursorGrab = false
+  let cursorVisible = true
+  let cursorX = null
+  let cursorY = null
+  let cursorIcon = 'default'
+  let cursorIgnoreEvents = false
+  let windowTitle = 'Awesome Tauri Example!'
+
+  let effects = []
+  let selectedEffect
+  let effectState
+  let effectRadius
+  let effectR, effectG, effectB, effectA
+
+  let selectedProgressBarStatus = 'none'
+  let progress = 0
+
+  let windowIconPath
 
   function setTitle_() {
-    windowMap[selectedWindow].setTitle(windowTitle);
+    windowMap[selectedWindow].setTitle(windowTitle)
   }
 
   function hide_() {
-    windowMap[selectedWindow].hide();
-    setTimeout(windowMap[selectedWindow].show, 2000);
+    windowMap[selectedWindow].hide()
+    setTimeout(windowMap[selectedWindow].show, 2000)
   }
 
   function minimize_() {
-    windowMap[selectedWindow].minimize();
-    setTimeout(windowMap[selectedWindow].unminimize, 2000);
+    windowMap[selectedWindow].minimize()
+    setTimeout(windowMap[selectedWindow].unminimize, 2000)
   }
 
   function changeIcon() {
-    windowMap[selectedWindow].setIcon(path);
+    windowMap[selectedWindow].setIcon(path)
   }
 
   function createWindow() {
-    if (!newWindowLabel) return;
+    if (!newWindowLabel) return
 
-    const webview = new Window(newWindowLabel);
-    windowMap[newWindowLabel] = webview;
-    webview.once("tauri://error", function () {
-      onMessage("Error creating new webview");
-    });
+    const webview = new Window(newWindowLabel)
+    windowMap[newWindowLabel] = webview
+    webview.once('tauri://error', function () {
+      onMessage('Error creating new webview')
+    })
   }
 
   function loadWindowSize() {
     windowMap[selectedWindow].innerSize().then((response) => {
-      innerSize = response;
-      width = innerSize.width;
-      height = innerSize.height;
-    });
+      innerSize = response
+      width = innerSize.width
+      height = innerSize.height
+    })
     windowMap[selectedWindow].outerSize().then((response) => {
-      outerSize = response;
-    });
+      outerSize = response
+    })
   }
 
   function loadWindowPosition() {
     windowMap[selectedWindow].innerPosition().then((response) => {
-      innerPosition = response;
-    });
+      innerPosition = response
+    })
     windowMap[selectedWindow].outerPosition().then((response) => {
-      outerPosition = response;
-      x = outerPosition.x;
-      y = outerPosition.y;
-    });
+      outerPosition = response
+      x = outerPosition.x
+      y = outerPosition.y
+    })
   }
 
   async function addWindowEventListeners(window) {
-    if (!window) return;
+    if (!window) return
     if (resizeEventUnlisten) {
-      resizeEventUnlisten();
+      resizeEventUnlisten()
     }
     if (moveEventUnlisten) {
-      moveEventUnlisten();
+      moveEventUnlisten()
     }
-    moveEventUnlisten = await window.listen("tauri://move", loadWindowPosition);
-    resizeEventUnlisten = await window.listen("tauri://resize", loadWindowSize);
+    moveEventUnlisten = await window.listen('tauri://move', loadWindowPosition)
+    resizeEventUnlisten = await window.listen('tauri://resize', loadWindowSize)
   }
 
   async function requestUserAttention_() {
-    await windowMap[selectedWindow].minimize();
+    await windowMap[selectedWindow].minimize()
     await windowMap[selectedWindow].requestUserAttention(
       UserAttentionType.Critical
-    );
-    await new Promise((resolve) => setTimeout(resolve, 3000));
-    await windowMap[selectedWindow].requestUserAttention(null);
+    )
+    await new Promise((resolve) => setTimeout(resolve, 3000))
+    await windowMap[selectedWindow].requestUserAttention(null)
   }
 
   async function addEffect() {
     if (!effects.includes(selectedEffect)) {
-      effects = [...effects, selectedEffect];
+      effects = [...effects, selectedEffect]
     }
 
     const payload = {
       effects,
       state: effectState,
-      radius: effectRadius,
-    };
+      radius: effectRadius
+    }
     if (
       Number.isInteger(effectR) &&
       Number.isInteger(effectG) &&
       Number.isInteger(effectB) &&
       Number.isInteger(effectA)
     ) {
-      payload.color = [effectR, effectG, effectB, effectA];
+      payload.color = [effectR, effectG, effectB, effectA]
     }
 
-    mainEl.classList.remove("bg-primary");
-    mainEl.classList.remove("dark:bg-darkPrimary");
-    await windowMap[selectedWindow].clearEffects();
-    await windowMap[selectedWindow].setEffects(payload);
+    mainEl.classList.remove('bg-primary')
+    mainEl.classList.remove('dark:bg-darkPrimary')
+    await windowMap[selectedWindow].clearEffects()
+    await windowMap[selectedWindow].setEffects(payload)
   }
 
   async function clearEffects() {
-    effects = [];
-    await windowMap[selectedWindow].clearEffects();
-    mainEl.classList.add("bg-primary");
-    mainEl.classList.add("dark:bg-darkPrimary");
+    effects = []
+    await windowMap[selectedWindow].clearEffects()
+    mainEl.classList.add('bg-primary')
+    mainEl.classList.add('dark:bg-darkPrimary')
   }
 
   $: {
-    windowMap[selectedWindow];
-    loadWindowPosition();
-    loadWindowSize();
+    windowMap[selectedWindow]
+    loadWindowPosition()
+    loadWindowSize()
   }
-  $: windowMap[selectedWindow]?.setResizable(resizable);
-  $: windowMap[selectedWindow]?.setMaximizable(maximizable);
-  $: windowMap[selectedWindow]?.setMinimizable(minimizable);
-  $: windowMap[selectedWindow]?.setClosable(closable);
+  $: windowMap[selectedWindow]?.setResizable(resizable)
+  $: windowMap[selectedWindow]?.setMaximizable(maximizable)
+  $: windowMap[selectedWindow]?.setMinimizable(minimizable)
+  $: windowMap[selectedWindow]?.setClosable(closable)
   $: maximized
     ? windowMap[selectedWindow]?.maximize()
-    : windowMap[selectedWindow]?.unmaximize();
-  $: windowMap[selectedWindow]?.setDecorations(decorations);
-  $: windowMap[selectedWindow]?.setAlwaysOnTop(alwaysOnTop);
-  $: windowMap[selectedWindow]?.setContentProtected(contentProtected);
-  $: windowMap[selectedWindow]?.setFullscreen(fullscreen);
+    : windowMap[selectedWindow]?.unmaximize()
+  $: windowMap[selectedWindow]?.setDecorations(decorations)
+  $: windowMap[selectedWindow]?.setAlwaysOnTop(alwaysOnTop)
+  $: windowMap[selectedWindow]?.setContentProtected(contentProtected)
+  $: windowMap[selectedWindow]?.setFullscreen(fullscreen)
 
   $: width &&
     height &&
-    windowMap[selectedWindow]?.setSize(new PhysicalSize(width, height));
+    windowMap[selectedWindow]?.setSize(new PhysicalSize(width, height))
   $: minWidth && minHeight
     ? windowMap[selectedWindow]?.setMinSize(
         new LogicalSize(minWidth, minHeight)
       )
-    : windowMap[selectedWindow]?.setMinSize(null);
+    : windowMap[selectedWindow]?.setMinSize(null)
   $: maxWidth > 800 && maxHeight > 400
     ? windowMap[selectedWindow]?.setMaxSize(
         new LogicalSize(maxWidth, maxHeight)
       )
-    : windowMap[selectedWindow]?.setMaxSize(null);
+    : windowMap[selectedWindow]?.setMaxSize(null)
   $: x !== null &&
     y !== null &&
-    windowMap[selectedWindow]?.setPosition(new PhysicalPosition(x, y));
+    windowMap[selectedWindow]?.setPosition(new PhysicalPosition(x, y))
   $: windowMap[selectedWindow]
     ?.scaleFactor()
-    .then((factor) => (scaleFactor = factor));
-  $: addWindowEventListeners(windowMap[selectedWindow]);
+    .then((factor) => (scaleFactor = factor))
+  $: addWindowEventListeners(windowMap[selectedWindow])
 
-  $: windowMap[selectedWindow]?.setCursorGrab(cursorGrab);
-  $: windowMap[selectedWindow]?.setCursorVisible(cursorVisible);
-  $: windowMap[selectedWindow]?.setCursorIcon(cursorIcon);
+  $: windowMap[selectedWindow]?.setCursorGrab(cursorGrab)
+  $: windowMap[selectedWindow]?.setCursorVisible(cursorVisible)
+  $: windowMap[selectedWindow]?.setCursorIcon(cursorIcon)
   $: cursorX !== null &&
     cursorY !== null &&
     windowMap[selectedWindow]?.setCursorPosition(
       new PhysicalPosition(cursorX, cursorY)
-    );
-  $: windowMap[selectedWindow]?.setIgnoreCursorEvents(cursorIgnoreEvents);
+    )
+  $: windowMap[selectedWindow]?.setIgnoreCursorEvents(cursorIgnoreEvents)
+  $: windowMap[selectedWindow]?.setProgressBar({ status: selectedProgressBarStatus, progress })
 </script>
 
 <div class="flex flex-col children:grow gap-2">
@@ -293,9 +307,7 @@
   {#if windowMap[selectedWindow]}
     <br />
     <div class="flex gap-1 items-center">
-       <label>
-        Icon path
-      </label>
+      <label> Icon path </label>
       <form class="flex gap-1 grow" on:submit|preventDefault={setTitle_}>
         <input class="input grow" bind:value={windowIconPath} />
         <button class="btn" type="submit"> Change window icon </button>
@@ -527,6 +539,24 @@
 
     <br />
 
+    <div class="flex flex-col gap-1">
+      <div class="flex gap-2">
+        <label>
+          Progress Status
+          <select class="input" bind:value={selectedProgressBarStatus}>
+            {#each progressBarStatusOptions as status}
+              <option value={status}>{status}</option>
+            {/each}
+          </select>
+        </label>
+
+        <label>
+          Progress
+          <input class="input" type="number" min="0" max="100" bind:value={progress} />
+        </label>
+      </div>
+    </div>
+
     {#if isWindows || isMacOS}
       <div class="flex flex-col gap-1">
         <div class="flex">
@@ -598,7 +628,7 @@
 
         <div class="flex">
           <div>
-            Applied effects: {effects.length ? effects.join(",") : "None"}
+            Applied effects: {effects.length ? effects.join(',') : 'None'}
           </div>
 
           <button class="btn" style="width: 80px;" on:click={clearEffects}

+ 5 - 1
examples/api/vite.config.js

@@ -9,7 +9,11 @@ import { internalIpV4 } from 'internal-ip'
 
 // https://vitejs.dev/config/
 export default defineConfig(async ({ command, mode }) => {
-  const host = process.env.TAURI_PLATFORM === 'android' || process.env.TAURI_PLATFORM === 'ios' ? (await internalIpV4()) : 'localhost'
+  const host =
+    process.env.TAURI_PLATFORM === 'android' ||
+    process.env.TAURI_PLATFORM === 'ios'
+      ? await internalIpV4()
+      : 'localhost'
   return {
     plugins: [Unocss(), svelte()],
     build: {

File diff suppressed because it is too large
+ 0 - 0
tooling/api/docs/js-api.json


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

@@ -148,6 +148,44 @@ export type CursorIcon =
   | 'colResize'
   | 'rowResize'
 
+export enum ProgressBarStatus {
+  /**
+   * Hide progress bar.
+   */
+  None = 'none',
+  /**
+   * Normal state.
+   */
+  Normal = 'normal',
+  /**
+   * Indeterminate state. **Treated as Normal on Linux and macOS**
+   */
+  Indeterminate = 'indeterminate',
+  /**
+   * Paused state. **Treated as Normal on Linux**
+   */
+  Paused = 'paused',
+  /**
+   * Error state. **Treated as Normal on linux**
+   */
+  Error = 'error'
+}
+
+export interface ProgressBarState {
+  /**
+   * The progress bar status.
+   */
+  status?: ProgressBarStatus
+  /**
+   * The progress bar progress. This can be a value ranging from `0` to `100`
+   */
+  progress?: number
+  /**
+   * The identifier for your app to communicate with the Unity desktop window manager **Linux Only**
+   */
+  unityUri?: string
+}
+
 /**
  * Get an instance of `Window` for the current window.
  *
@@ -1446,6 +1484,32 @@ class Window {
     })
   }
 
+  /**
+   * Sets the taskbar progress state.
+   *
+   * #### Platform-specific
+   *
+   * - **Linux / macOS**: Progress bar is app-wide and not specific to this window.
+   * - **Linux**: Only supported desktop environments with `libunity` (e.g. GNOME).
+   *
+   * @example
+   * ```typescript
+   * import { getCurrent, ProgressBarStatus } from '@tauri-apps/api/window';
+   * await getCurrent().setProgressBar({
+   *   status: ProgressBarStatus.Normal,
+   *   progress: 50,
+   * });
+   * ```
+   *
+   * @return A promise indicating the success or failure of the operation.
+   */
+  async setProgressBar(state: ProgressBarState): Promise<void> {
+    return invoke('plugin:window|set_progress_bar', {
+      label: this.label,
+      value: state
+    })
+  }
+
   // Listeners
 
   /**

Some files were not shown because too many files changed in this diff