feat: move window plugin back to core (#8007)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Amr Bashir 2023-10-17 14:33:23 +03:00 committed by GitHub
parent f12306af5f
commit c9a9246c37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 3388 additions and 17 deletions

5
.changes/api-window.md Normal file
View File

@ -0,0 +1,5 @@
---
"@tauri-apps/api": 'minor:feat'
---
Add the `window` module back

View File

@ -0,0 +1,5 @@
---
"tauri": patch:changes
---
Added the `window` plugin back into core.

File diff suppressed because one or more lines are too long

View File

@ -807,6 +807,7 @@ impl<R: Runtime> App<R> {
fn register_core_plugins(&self) -> crate::Result<()> {
self.handle.plugin(crate::path::init())?;
self.handle.plugin(crate::event::init())?;
self.handle.plugin(crate::window::plugin::init())?;
Ok(())
}

View File

@ -107,4 +107,7 @@ pub enum Error {
#[cfg(all(desktop, feature = "tray-icon"))]
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "tray-icon"))))]
BadTrayIcon(#[from] tray_icon::BadIcon),
/// window not found.
#[error("window not found")]
WindowNotFound,
}

View File

@ -4,6 +4,8 @@
//! The Tauri window types and functions.
pub(crate) mod plugin;
use http::HeaderMap;
pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState};
use url::Url;
@ -2461,7 +2463,7 @@ impl<R: Runtime> Window<R> {
///
/// This listener only receives events that are triggered using the
/// [`trigger`](Window#method.trigger) and [`emit_and_trigger`](Window#method.emit_and_trigger) methods or
/// the `emit` function from the window plugin (`@tauri-apps/plugin-window` package).
/// the `emit` function from the window plugin (`@tauri-apps/api/window` package).
///
/// # Examples
/// ```

View File

@ -0,0 +1,322 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! The tauri plugin to create and manipulate windows from JS.
use crate::{
plugin::{Builder, TauriPlugin},
Runtime,
};
#[cfg(desktop)]
mod desktop_commands {
use serde::Deserialize;
use super::*;
use crate::{
command,
utils::config::{WindowConfig, WindowEffectsConfig},
AppHandle, CursorIcon, Icon, Manager, Monitor, PhysicalPosition, PhysicalSize, Position, Size,
Theme, UserAttentionType, Window, WindowBuilder,
};
#[derive(Deserialize)]
#[serde(untagged)]
pub enum IconDto {
#[cfg(any(feature = "icon-png", feature = "icon-ico"))]
File(std::path::PathBuf),
#[cfg(any(feature = "icon-png", feature = "icon-ico"))]
Raw(Vec<u8>),
Rgba {
rgba: Vec<u8>,
width: u32,
height: u32,
},
}
impl From<IconDto> for Icon {
fn from(icon: IconDto) -> Self {
match icon {
#[cfg(any(feature = "icon-png", feature = "icon-ico"))]
IconDto::File(path) => Self::File(path),
#[cfg(any(feature = "icon-png", feature = "icon-ico"))]
IconDto::Raw(raw) => Self::Raw(raw),
IconDto::Rgba {
rgba,
width,
height,
} => Self::Rgba {
rgba,
width,
height,
},
}
}
}
#[command(root = "crate")]
pub async fn create<R: Runtime>(app: AppHandle<R>, options: WindowConfig) -> crate::Result<()> {
WindowBuilder::from_config(&app, options).build()?;
Ok(())
}
fn get_window<R: Runtime>(window: Window<R>, label: Option<String>) -> crate::Result<Window<R>> {
match label {
Some(l) if !l.is_empty() => window.get_window(&l).ok_or(crate::Error::WindowNotFound),
_ => Ok(window),
}
}
macro_rules! getter {
($cmd: ident, $ret: ty) => {
#[command(root = "crate")]
pub async fn $cmd<R: Runtime>(
window: Window<R>,
label: Option<String>,
) -> crate::Result<$ret> {
get_window(window, label)?.$cmd().map_err(Into::into)
}
};
}
macro_rules! setter {
($cmd: ident) => {
#[command(root = "crate")]
pub async fn $cmd<R: Runtime>(window: Window<R>, label: Option<String>) -> crate::Result<()> {
get_window(window, label)?.$cmd().map_err(Into::into)
}
};
($cmd: ident, $input: ty) => {
#[command(root = "crate")]
pub async fn $cmd<R: Runtime>(
window: Window<R>,
label: Option<String>,
value: $input,
) -> crate::Result<()> {
get_window(window, label)?.$cmd(value).map_err(Into::into)
}
};
}
getter!(scale_factor, f64);
getter!(inner_position, PhysicalPosition<i32>);
getter!(outer_position, PhysicalPosition<i32>);
getter!(inner_size, PhysicalSize<u32>);
getter!(outer_size, PhysicalSize<u32>);
getter!(is_fullscreen, bool);
getter!(is_minimized, bool);
getter!(is_maximized, bool);
getter!(is_focused, bool);
getter!(is_decorated, bool);
getter!(is_resizable, bool);
getter!(is_maximizable, bool);
getter!(is_minimizable, bool);
getter!(is_closable, bool);
getter!(is_visible, bool);
getter!(title, String);
getter!(current_monitor, Option<Monitor>);
getter!(primary_monitor, Option<Monitor>);
getter!(available_monitors, Vec<Monitor>);
getter!(theme, Theme);
setter!(center);
setter!(request_user_attention, Option<UserAttentionType>);
setter!(set_resizable, bool);
setter!(set_maximizable, bool);
setter!(set_minimizable, bool);
setter!(set_closable, bool);
setter!(set_title, &str);
setter!(maximize);
setter!(unmaximize);
setter!(minimize);
setter!(unminimize);
setter!(show);
setter!(hide);
setter!(close);
setter!(set_decorations, bool);
setter!(set_shadow, bool);
setter!(set_effects, Option<WindowEffectsConfig>);
setter!(set_always_on_top, bool);
setter!(set_content_protected, bool);
setter!(set_size, Size);
setter!(set_min_size, Option<Size>);
setter!(set_max_size, Option<Size>);
setter!(set_position, Position);
setter!(set_fullscreen, bool);
setter!(set_focus);
setter!(set_skip_taskbar, bool);
setter!(set_cursor_grab, bool);
setter!(set_cursor_visible, bool);
setter!(set_cursor_icon, CursorIcon);
setter!(set_cursor_position, Position);
setter!(set_ignore_cursor_events, bool);
setter!(start_dragging);
setter!(print);
#[command(root = "crate")]
pub async fn set_icon<R: Runtime>(
window: Window<R>,
label: Option<String>,
value: IconDto,
) -> crate::Result<()> {
get_window(window, label)?
.set_icon(value.into())
.map_err(Into::into)
}
#[command(root = "crate")]
pub async fn toggle_maximize<R: Runtime>(
window: Window<R>,
label: Option<String>,
) -> crate::Result<()> {
let window = get_window(window, label)?;
match window.is_maximized()? {
true => window.unmaximize()?,
false => window.maximize()?,
};
Ok(())
}
#[command(root = "crate")]
pub async fn internal_toggle_maximize<R: Runtime>(
window: Window<R>,
label: Option<String>,
) -> crate::Result<()> {
let window = get_window(window, label)?;
if window.is_resizable()? {
match window.is_maximized()? {
true => window.unmaximize()?,
false => window.maximize()?,
};
}
Ok(())
}
#[cfg(any(debug_assertions, feature = "devtools"))]
#[command(root = "crate")]
pub async fn internal_toggle_devtools<R: Runtime>(
window: Window<R>,
label: Option<String>,
) -> crate::Result<()> {
let window = get_window(window, label)?;
if window.is_devtools_open() {
window.close_devtools();
} else {
window.open_devtools();
}
Ok(())
}
}
/// Initializes the plugin.
pub fn init<R: Runtime>() -> TauriPlugin<R> {
let mut init_script = String::new();
// window.print works on Linux/Windows; need to use the API on macOS
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
init_script.push_str(include_str!("./scripts/print.js"));
}
init_script.push_str(include_str!("./scripts/drag.js"));
#[cfg(any(debug_assertions, feature = "devtools"))]
{
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
#[derive(Template)]
#[default_template("./scripts/toggle-devtools.js")]
struct Devtools<'a> {
os_name: &'a str,
}
init_script.push_str(
&Devtools {
os_name: std::env::consts::OS,
}
.render_default(&Default::default())
.unwrap()
.into_string(),
);
}
Builder::new("window")
.js_init_script(init_script)
.invoke_handler(|invoke| {
#[cfg(desktop)]
{
let handler: Box<dyn Fn(crate::ipc::Invoke<R>) -> bool> =
Box::new(crate::generate_handler![
desktop_commands::create,
// getters
desktop_commands::scale_factor,
desktop_commands::inner_position,
desktop_commands::outer_position,
desktop_commands::inner_size,
desktop_commands::outer_size,
desktop_commands::is_fullscreen,
desktop_commands::is_minimized,
desktop_commands::is_maximized,
desktop_commands::is_focused,
desktop_commands::is_decorated,
desktop_commands::is_resizable,
desktop_commands::is_maximizable,
desktop_commands::is_minimizable,
desktop_commands::is_closable,
desktop_commands::is_visible,
desktop_commands::title,
desktop_commands::current_monitor,
desktop_commands::primary_monitor,
desktop_commands::available_monitors,
desktop_commands::theme,
// setters
desktop_commands::center,
desktop_commands::request_user_attention,
desktop_commands::set_resizable,
desktop_commands::set_maximizable,
desktop_commands::set_minimizable,
desktop_commands::set_closable,
desktop_commands::set_title,
desktop_commands::maximize,
desktop_commands::unmaximize,
desktop_commands::minimize,
desktop_commands::unminimize,
desktop_commands::show,
desktop_commands::hide,
desktop_commands::close,
desktop_commands::set_decorations,
desktop_commands::set_shadow,
desktop_commands::set_effects,
desktop_commands::set_always_on_top,
desktop_commands::set_content_protected,
desktop_commands::set_size,
desktop_commands::set_min_size,
desktop_commands::set_max_size,
desktop_commands::set_position,
desktop_commands::set_fullscreen,
desktop_commands::set_focus,
desktop_commands::set_skip_taskbar,
desktop_commands::set_cursor_grab,
desktop_commands::set_cursor_visible,
desktop_commands::set_cursor_icon,
desktop_commands::set_cursor_position,
desktop_commands::set_ignore_cursor_events,
desktop_commands::start_dragging,
desktop_commands::print,
desktop_commands::set_icon,
desktop_commands::toggle_maximize,
desktop_commands::internal_toggle_maximize,
#[cfg(any(debug_assertions, feature = "devtools"))]
desktop_commands::internal_toggle_devtools,
]);
#[allow(clippy::needless_return)]
return handler(invoke);
}
#[cfg(mobile)]
{
invoke.resolver.reject("Window API not available on mobile");
return true;
}
})
.build()
}

View File

@ -0,0 +1,17 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
document.addEventListener("mousedown", (e) => {
if (e.target.hasAttribute("data-tauri-drag-region") && e.button === 0) {
// prevents text cursor
e.preventDefault();
// fix #2549: double click on drag region edge causes content to maximize without window sizing change
// https://github.com/tauri-apps/tauri/issues/2549#issuecomment-1250036908
e.stopImmediatePropagation();
// start dragging if the element has a `tauri-drag-region` data attribute and maximize on double-clicking it
const cmd = e.detail === 2 ? "internal_toggle_maximize" : "start_dragging";
window.__TAURI_INVOKE__("plugin:window|" + cmd);
}
});

View File

@ -0,0 +1,7 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
window.print = function () {
return window.__TAURI_INVOKE__("plugin:window|print");
};

View File

@ -0,0 +1,29 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
;(function () {
function toggleDevtoolsHotkey() {
const osName = __TEMPLATE_os_name__
const isHotkey =
osName === 'macos'
? (event) => event.metaKey && event.altKey && event.key === 'I'
: (event) => event.ctrlKey && event.shiftKey && event.key === 'I'
document.addEventListener('keydown', (event) => {
if (isHotkey(event)) {
window.__TAURI_INVOKE__('plugin:window|internal_toggle_devtools')
}
})
}
if (
document.readyState === 'complete' ||
document.readyState === 'interactive'
) {
toggleDevtoolsHotkey()
} else {
window.addEventListener('DOMContentLoaded', toggleDevtoolsHotkey, true)
}
})()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3333,9 +3333,9 @@ dependencies = [
[[package]]
name = "tao"
version = "0.22.0"
version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60279ecb16c33a6cef45cd37a9602455c190942d20e360bd8499bff49f2a48f3"
checksum = "f5d30690a6746dfbb26d4f41522757da1ebfd277514b58e27a80b7e55e511e52"
dependencies = [
"bitflags 1.3.2",
"cairo-rs",

View File

@ -5,6 +5,7 @@
import Welcome from './views/Welcome.svelte'
import Communication from './views/Communication.svelte'
import Window from './views/Window.svelte'
import WebRTC from './views/WebRTC.svelte'
document.addEventListener('keydown', (event) => {
@ -24,6 +25,11 @@
component: Communication,
icon: 'i-codicon-radio-tower'
},
{
label: 'Window',
component: Window,
icon: 'i-codicon-window'
},
{
label: 'WebRTC',
component: WebRTC,

View File

@ -0,0 +1,611 @@
<script>
import {
getCurrent,
LogicalSize,
UserAttentionType,
PhysicalSize,
PhysicalPosition,
Effect,
EffectState,
Window
} from "@tauri-apps/api/window";
import { invoke } from "@tauri-apps/api/tauri";
const appWindow = getCurrent();
let selectedWindow = appWindow.label;
const windowMap = {
[appWindow.label]: appWindow,
};
const cursorIconOptions = [
"default",
"crosshair",
"hand",
"arrow",
"move",
"text",
"wait",
"help",
"progress",
// something cannot be done
"notAllowed",
"contextMenu",
"cell",
"verticalText",
"alias",
"copy",
"noDrop",
// something can be grabbed
"grab",
/// something is grabbed
"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"];
const isWindows = navigator.appVersion.includes("windows");
const isMacOS = navigator.appVersion.includes("macos");
let effectOptions = isWindows
? windowsEffects
: Object.keys(Effect)
.map((effect) => Effect[effect])
.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;
function setTitle_() {
windowMap[selectedWindow].setTitle(windowTitle);
}
function hide_() {
windowMap[selectedWindow].hide();
setTimeout(windowMap[selectedWindow].show, 2000);
}
function minimize_() {
windowMap[selectedWindow].minimize();
setTimeout(windowMap[selectedWindow].unminimize, 2000);
}
function changeIcon() {
windowMap[selectedWindow].setIcon(path);
}
function createWindow() {
if (!newWindowLabel) return;
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;
});
windowMap[selectedWindow].outerSize().then((response) => {
outerSize = response;
});
}
function loadWindowPosition() {
windowMap[selectedWindow].innerPosition().then((response) => {
innerPosition = response;
});
windowMap[selectedWindow].outerPosition().then((response) => {
outerPosition = response;
x = outerPosition.x;
y = outerPosition.y;
});
}
async function addWindowEventListeners(window) {
if (!window) return;
if (resizeEventUnlisten) {
resizeEventUnlisten();
}
if (moveEventUnlisten) {
moveEventUnlisten();
}
moveEventUnlisten = await window.listen("tauri://move", loadWindowPosition);
resizeEventUnlisten = await window.listen("tauri://resize", loadWindowSize);
}
async function requestUserAttention_() {
await windowMap[selectedWindow].minimize();
await windowMap[selectedWindow].requestUserAttention(
UserAttentionType.Critical
);
await new Promise((resolve) => setTimeout(resolve, 3000));
await windowMap[selectedWindow].requestUserAttention(null);
}
async function addEffect() {
if (!effects.includes(selectedEffect)) {
effects = [...effects, selectedEffect];
}
const payload = {
effects,
state: effectState,
radius: effectRadius,
};
if (
Number.isInteger(effectR) &&
Number.isInteger(effectG) &&
Number.isInteger(effectB) &&
Number.isInteger(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);
}
async function clearEffects() {
effects = [];
await windowMap[selectedWindow].clearEffects();
mainEl.classList.add("bg-primary");
mainEl.classList.add("dark:bg-darkPrimary");
}
$: {
windowMap[selectedWindow];
loadWindowPosition();
loadWindowSize();
}
$: 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);
$: width &&
height &&
windowMap[selectedWindow]?.setSize(new PhysicalSize(width, height));
$: minWidth && minHeight
? windowMap[selectedWindow]?.setMinSize(
new LogicalSize(minWidth, minHeight)
)
: windowMap[selectedWindow]?.setMinSize(null);
$: maxWidth > 800 && maxHeight > 400
? windowMap[selectedWindow]?.setMaxSize(
new LogicalSize(maxWidth, maxHeight)
)
: windowMap[selectedWindow]?.setMaxSize(null);
$: x !== null &&
y !== null &&
windowMap[selectedWindow]?.setPosition(new PhysicalPosition(x, y));
$: windowMap[selectedWindow]
?.scaleFactor()
.then((factor) => (scaleFactor = factor));
$: addWindowEventListeners(windowMap[selectedWindow]);
$: 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);
</script>
<div class="flex flex-col children:grow gap-2">
<div class="flex gap-1">
<input
class="input grow"
type="text"
placeholder="New Window label.."
bind:value={newWindowLabel}
/>
<button class="btn" on:click={createWindow}>New window</button>
</div>
<br />
{#if Object.keys(windowMap).length >= 1}
<span class="font-700 text-sm">Selected window:</span>
<select class="input" bind:value={selectedWindow}>
<option value="" disabled selected>Choose a window...</option>
{#each Object.keys(windowMap) as label}
<option value={label}>{label}</option>
{/each}
</select>
{/if}
{#if windowMap[selectedWindow]}
<br />
<div class="flex gap-1 items-center">
<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>
</form>
</div>
<br />
<div class="flex flex-wrap gap-2">
<button
class="btn"
title="Unminimizes after 2 seconds"
on:click={() => windowMap[selectedWindow].center()}
>
Center
</button>
<button
class="btn"
title="Unminimizes after 2 seconds"
on:click={minimize_}
>
Minimize
</button>
<button
class="btn"
title="Visible again after 2 seconds"
on:click={hide_}
>
Hide
</button>
<button
class="btn"
on:click={requestUserAttention_}
title="Minimizes the window, requests attention for 3s and then resets it"
>Request attention</button
>
</div>
<div class="flex flex-wrap gap-2">
<label>
Maximized
<input type="checkbox" bind:checked={maximized} />
</label>
<label>
Resizable
<input type="checkbox" bind:checked={resizable} />
</label>
<label>
Maximizable
<input type="checkbox" bind:checked={maximizable} />
</label>
<label>
Minimizable
<input type="checkbox" bind:checked={minimizable} />
</label>
<label>
Closable
<input type="checkbox" bind:checked={closable} />
</label>
<label>
Has decorations
<input type="checkbox" bind:checked={decorations} />
</label>
<label>
Always on top
<input type="checkbox" bind:checked={alwaysOnTop} />
</label>
<label>
Content protected
<input type="checkbox" bind:checked={contentProtected} />
</label>
<label>
Fullscreen
<input type="checkbox" bind:checked={fullscreen} />
</label>
</div>
<br />
<div class="flex flex-row gap-2 flex-wrap">
<div class="flex children:grow flex-col">
<div>
X
<input class="input" type="number" bind:value={x} min="0" />
</div>
<div>
Y
<input class="input" type="number" bind:value={y} min="0" />
</div>
</div>
<div class="flex children:grow flex-col">
<div>
Width
<input class="input" type="number" bind:value={width} min="400" />
</div>
<div>
Height
<input class="input" type="number" bind:value={height} min="400" />
</div>
</div>
<div class="flex children:grow flex-col">
<div>
Min width
<input class="input" type="number" bind:value={minWidth} />
</div>
<div>
Min height
<input class="input" type="number" bind:value={minHeight} />
</div>
</div>
<div class="flex children:grow flex-col">
<div>
Max width
<input class="input" type="number" bind:value={maxWidth} min="800" />
</div>
<div>
Max height
<input class="input" type="number" bind:value={maxHeight} min="400" />
</div>
</div>
</div>
<br />
<div>
<div class="flex">
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Inner Size
</div>
<span>Width: {innerSize.width}</span>
<span>Height: {innerSize.height}</span>
</div>
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Outer Size
</div>
<span>Width: {outerSize.width}</span>
<span>Height: {outerSize.height}</span>
</div>
</div>
<div class="flex">
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Inner Logical Size
</div>
<span>Width: {innerSize.toLogical(scaleFactor).width}</span>
<span>Height: {innerSize.toLogical(scaleFactor).height}</span>
</div>
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Outer Logical Size
</div>
<span>Width: {outerSize.toLogical(scaleFactor).width}</span>
<span>Height: {outerSize.toLogical(scaleFactor).height}</span>
</div>
</div>
<div class="flex">
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Inner Position
</div>
<span>x: {innerPosition.x}</span>
<span>y: {innerPosition.y}</span>
</div>
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Outer Position
</div>
<span>x: {outerPosition.x}</span>
<span>y: {outerPosition.y}</span>
</div>
</div>
<div class="flex">
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Inner Logical Position
</div>
<span>x: {innerPosition.toLogical(scaleFactor).x}</span>
<span>y: {innerPosition.toLogical(scaleFactor).y}</span>
</div>
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Outer Logical Position
</div>
<span>x: {outerPosition.toLogical(scaleFactor).x}</span>
<span>y: {outerPosition.toLogical(scaleFactor).y}</span>
</div>
</div>
</div>
<br />
<h4 class="mb-2">Cursor</h4>
<div class="flex gap-2">
<label>
<input type="checkbox" bind:checked={cursorGrab} />
Grab
</label>
<label>
<input type="checkbox" bind:checked={cursorVisible} />
Visible
</label>
<label>
<input type="checkbox" bind:checked={cursorIgnoreEvents} />
Ignore events
</label>
</div>
<div class="flex gap-2">
<label>
Icon
<select class="input" bind:value={cursorIcon}>
{#each cursorIconOptions as kind}
<option value={kind}>{kind}</option>
{/each}
</select>
</label>
<label>
X position
<input class="input" type="number" bind:value={cursorX} />
</label>
<label>
Y position
<input class="input" type="number" bind:value={cursorY} />
</label>
</div>
<br />
<div class="flex flex-col gap-1">
<form class="flex gap-1" on:submit|preventDefault={setTitle_}>
<input class="input grow" id="title" bind:value={windowTitle} />
<button class="btn" type="submit">Set title</button>
</form>
</div>
<br />
{#if isWindows || isMacOS}
<div class="flex flex-col gap-1">
<div class="flex">
<label>
Effect
<select class="input" bind:value={selectedEffect}>
{#each effectOptions as effect}
<option value={effect}>{effect}</option>
{/each}
</select>
</label>
<label>
State
<select class="input" bind:value={effectState}>
{#each effectStateOptions as state}
<option value={state}>{state}</option>
{/each}
</select>
</label>
<label>
Radius
<input class="input" type="number" bind:value={effectRadius} />
</label>
</div>
<div class="flex">
<label>
Color
<div class="flex">
<input
style="max-width: 120px;"
class="input"
type="number"
placeholder="R"
bind:value={effectR}
/>
<input
style="max-width: 120px;"
class="input"
type="number"
placeholder="G"
bind:value={effectG}
/>
<input
style="max-width: 120px;"
class="input"
type="number"
placeholder="B"
bind:value={effectB}
/>
<input
style="max-width: 120px;"
class="input"
type="number"
placeholder="A"
bind:value={effectA}
/>
</div>
</label>
</div>
<div class="flex">
<button class="btn" style="width: 80px;" on:click={addEffect}
>Add</button
>
</div>
<div class="flex">
<div>
Applied effects: {effects.length ? effects.join(",") : "None"}
</div>
<button class="btn" style="width: 80px;" on:click={clearEffects}
>Clear</button
>
</div>
</div>
{/if}
{/if}
</div>

File diff suppressed because one or more lines are too long

99
tooling/api/src/dpi.ts Normal file
View File

@ -0,0 +1,99 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
/**
* A size represented in logical pixels.
*
* @since 2.0.0
*/
class LogicalSize {
type = 'Logical'
width: number
height: number
constructor(width: number, height: number) {
this.width = width
this.height = height
}
}
/**
* A size represented in physical pixels.
*
* @since 2.0.0
*/
class PhysicalSize {
type = 'Physical'
width: number
height: number
constructor(width: number, height: number) {
this.width = width
this.height = height
}
/**
* Converts the physical size to a logical one.
* @example
* ```typescript
* import { getCurrent } from '@tauri-apps/api/window';
* const appWindow = getCurrent();
* const factor = await appWindow.scaleFactor();
* const size = await appWindow.innerSize();
* const logical = size.toLogical(factor);
* ```
* */
toLogical(scaleFactor: number): LogicalSize {
return new LogicalSize(this.width / scaleFactor, this.height / scaleFactor)
}
}
/**
* A position represented in logical pixels.
*
* @since 2.0.0
*/
class LogicalPosition {
type = 'Logical'
x: number
y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
}
/**
* A position represented in physical pixels.
*
* @since 2.0.0
*/
class PhysicalPosition {
type = 'Physical'
x: number
y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
/**
* Converts the physical position to a logical one.
* @example
* ```typescript
* import { getCurrent } from '@tauri-apps/api/window';
* const appWindow = getCurrent();
* const factor = await appWindow.scaleFactor();
* const position = await appWindow.innerPosition();
* const logical = position.toLogical(factor);
* ```
* */
toLogical(scaleFactor: number): LogicalPosition {
return new LogicalPosition(this.x / scaleFactor, this.y / scaleFactor)
}
}
export { LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize }

View File

@ -8,16 +8,18 @@
* This module exposes all other modules as an object where the key is the module name, and the value is the module exports.
* @example
* ```typescript
* import { app, dialog, event, fs, globalShortcut } from '@tauri-apps/api'
* import { event, window, path } from '@tauri-apps/api'
* ```
* @module
*/
import * as event from './event'
import * as tauri from './tauri'
import * as window from './window'
import * as path from './path'
import * as dpi from './dpi'
/** @ignore */
const invoke = tauri.invoke
export { invoke, event, path, tauri }
export { invoke, event, path, tauri, window, dpi }

2234
tooling/api/src/window.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,8 @@
"src/event.ts",
"src/mocks.ts",
"src/path.ts",
"src/tauri.ts"
"src/tauri.ts",
"src/window.ts"
],
"githubPages": false,
"readme": "none",