Window.svelte 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. <script>
  2. import {
  3. appWindow,
  4. WebviewWindow,
  5. LogicalSize,
  6. UserAttentionType,
  7. PhysicalSize,
  8. PhysicalPosition
  9. } from '@tauri-apps/api/window'
  10. import { open as openDialog } from '@tauri-apps/api/dialog'
  11. import { open } from '@tauri-apps/api/shell'
  12. let selectedWindow = appWindow.label
  13. const windowMap = {
  14. [appWindow.label]: appWindow
  15. }
  16. const cursorIconOptions = [
  17. 'default',
  18. 'crosshair',
  19. 'hand',
  20. 'arrow',
  21. 'move',
  22. 'text',
  23. 'wait',
  24. 'help',
  25. 'progress',
  26. // something cannot be done
  27. 'notAllowed',
  28. 'contextMenu',
  29. 'cell',
  30. 'verticalText',
  31. 'alias',
  32. 'copy',
  33. 'noDrop',
  34. // something can be grabbed
  35. 'grab',
  36. /// something is grabbed
  37. 'grabbing',
  38. 'allScroll',
  39. 'zoomIn',
  40. 'zoomOut',
  41. // edge is to be moved
  42. 'eResize',
  43. 'nResize',
  44. 'neResize',
  45. 'nwResize',
  46. 'sResize',
  47. 'seResize',
  48. 'swResize',
  49. 'wResize',
  50. 'ewResize',
  51. 'nsResize',
  52. 'neswResize',
  53. 'nwseResize',
  54. 'colResize',
  55. 'rowResize'
  56. ]
  57. export let onMessage
  58. let newWindowLabel
  59. let urlValue = 'https://tauri.app'
  60. let resizable = true
  61. let maximized = false
  62. let decorations = true
  63. let alwaysOnTop = false
  64. let contentProtected = true
  65. let fullscreen = false
  66. let width = null
  67. let height = null
  68. let minWidth = null
  69. let minHeight = null
  70. let maxWidth = null
  71. let maxHeight = null
  72. let x = null
  73. let y = null
  74. let scaleFactor = 1
  75. let innerPosition = new PhysicalPosition(x, y)
  76. let outerPosition = new PhysicalPosition(x, y)
  77. let innerSize = new PhysicalSize(width, height)
  78. let outerSize = new PhysicalSize(width, height)
  79. let resizeEventUnlisten
  80. let moveEventUnlisten
  81. let cursorGrab = false
  82. let cursorVisible = true
  83. let cursorX = null
  84. let cursorY = null
  85. let cursorIcon = 'default'
  86. let cursorIgnoreEvents = false
  87. let windowTitle = 'Awesome Tauri Example!'
  88. function openUrl() {
  89. open(urlValue)
  90. }
  91. function setTitle_() {
  92. windowMap[selectedWindow].setTitle(windowTitle)
  93. }
  94. function hide_() {
  95. windowMap[selectedWindow].hide()
  96. setTimeout(windowMap[selectedWindow].show, 2000)
  97. }
  98. function minimize_() {
  99. windowMap[selectedWindow].minimize()
  100. setTimeout(windowMap[selectedWindow].unminimize, 2000)
  101. }
  102. function getIcon() {
  103. openDialog({
  104. multiple: false
  105. }).then((path) => {
  106. if (typeof path === 'string') {
  107. windowMap[selectedWindow].setIcon(path)
  108. }
  109. })
  110. }
  111. function createWindow() {
  112. if (!newWindowLabel) return
  113. const webview = new WebviewWindow(newWindowLabel)
  114. windowMap[newWindowLabel] = webview
  115. webview.once('tauri://error', function () {
  116. onMessage('Error creating new webview')
  117. })
  118. }
  119. function loadWindowSize() {
  120. windowMap[selectedWindow].innerSize().then((response) => {
  121. innerSize = response
  122. width = innerSize.width
  123. height = innerSize.height
  124. })
  125. windowMap[selectedWindow].outerSize().then((response) => {
  126. outerSize = response
  127. })
  128. }
  129. function loadWindowPosition() {
  130. windowMap[selectedWindow].innerPosition().then((response) => {
  131. innerPosition = response
  132. })
  133. windowMap[selectedWindow].outerPosition().then((response) => {
  134. outerPosition = response
  135. x = outerPosition.x
  136. y = outerPosition.y
  137. })
  138. }
  139. async function addWindowEventListeners(window) {
  140. if (!window) return
  141. if (resizeEventUnlisten) {
  142. resizeEventUnlisten()
  143. }
  144. if (moveEventUnlisten) {
  145. moveEventUnlisten()
  146. }
  147. moveEventUnlisten = await window.listen('tauri://move', loadWindowPosition)
  148. resizeEventUnlisten = await window.listen('tauri://resize', loadWindowSize)
  149. }
  150. async function requestUserAttention_() {
  151. await windowMap[selectedWindow].minimize()
  152. await windowMap[selectedWindow].requestUserAttention(
  153. UserAttentionType.Critical
  154. )
  155. await new Promise((resolve) => setTimeout(resolve, 3000))
  156. await windowMap[selectedWindow].requestUserAttention(null)
  157. }
  158. $: {
  159. windowMap[selectedWindow]
  160. loadWindowPosition()
  161. loadWindowSize()
  162. }
  163. $: windowMap[selectedWindow]?.setResizable(resizable)
  164. $: maximized
  165. ? windowMap[selectedWindow]?.maximize()
  166. : windowMap[selectedWindow]?.unmaximize()
  167. $: windowMap[selectedWindow]?.setDecorations(decorations)
  168. $: windowMap[selectedWindow]?.setAlwaysOnTop(alwaysOnTop)
  169. $: windowMap[selectedWindow]?.setContentProtected(contentProtected)
  170. $: windowMap[selectedWindow]?.setFullscreen(fullscreen)
  171. $: width &&
  172. height &&
  173. windowMap[selectedWindow]?.setSize(new PhysicalSize(width, height))
  174. $: minWidth && minHeight
  175. ? windowMap[selectedWindow]?.setMinSize(
  176. new LogicalSize(minWidth, minHeight)
  177. )
  178. : windowMap[selectedWindow]?.setMinSize(null)
  179. $: maxWidth > 800 && maxHeight > 400
  180. ? windowMap[selectedWindow]?.setMaxSize(
  181. new LogicalSize(maxWidth, maxHeight)
  182. )
  183. : windowMap[selectedWindow]?.setMaxSize(null)
  184. $: x !== null &&
  185. y !== null &&
  186. windowMap[selectedWindow]?.setPosition(new PhysicalPosition(x, y))
  187. $: windowMap[selectedWindow]
  188. ?.scaleFactor()
  189. .then((factor) => (scaleFactor = factor))
  190. $: addWindowEventListeners(windowMap[selectedWindow])
  191. $: windowMap[selectedWindow]?.setCursorGrab(cursorGrab)
  192. $: windowMap[selectedWindow]?.setCursorVisible(cursorVisible)
  193. $: windowMap[selectedWindow]?.setCursorIcon(cursorIcon)
  194. $: cursorX !== null &&
  195. cursorY !== null &&
  196. windowMap[selectedWindow]?.setCursorPosition(
  197. new PhysicalPosition(cursorX, cursorY)
  198. )
  199. $: windowMap[selectedWindow]?.setIgnoreCursorEvents(cursorIgnoreEvents)
  200. </script>
  201. <div class="flex flex-col children:grow gap-2">
  202. <div class="flex gap-1">
  203. <input
  204. class="input grow"
  205. type="text"
  206. placeholder="New Window label.."
  207. bind:value={newWindowLabel}
  208. />
  209. <button class="btn" on:click={createWindow}>New window</button>
  210. </div>
  211. <br />
  212. {#if Object.keys(windowMap).length >= 1}
  213. <span class="font-700 text-sm">Selected window:</span>
  214. <select class="input" bind:value={selectedWindow}>
  215. <option value="" disabled selected>Choose a window...</option>
  216. {#each Object.keys(windowMap) as label}
  217. <option value={label}>{label}</option>
  218. {/each}
  219. </select>
  220. {/if}
  221. {#if windowMap[selectedWindow]}
  222. <br />
  223. <div class="flex flex-wrap gap-2">
  224. <button
  225. class="btn"
  226. title="Unminimizes after 2 seconds"
  227. on:click={() => windowMap[selectedWindow].center()}
  228. >
  229. Center
  230. </button>
  231. <button
  232. class="btn"
  233. title="Unminimizes after 2 seconds"
  234. on:click={minimize_}
  235. >
  236. Minimize
  237. </button>
  238. <button
  239. class="btn"
  240. title="Visible again after 2 seconds"
  241. on:click={hide_}
  242. >
  243. Hide
  244. </button>
  245. <button class="btn" on:click={getIcon}> Change icon </button>
  246. <button
  247. class="btn"
  248. on:click={requestUserAttention_}
  249. title="Minimizes the window, requests attention for 3s and then resets it"
  250. >Request attention</button
  251. >
  252. </div>
  253. <br />
  254. <div class="flex flex-wrap gap-2">
  255. <label>
  256. Maximized
  257. <input type="checkbox" bind:checked={maximized} />
  258. </label>
  259. <label>
  260. Resizable
  261. <input type="checkbox" bind:checked={resizable} />
  262. </label>
  263. <label>
  264. Has decorations
  265. <input type="checkbox" bind:checked={decorations} />
  266. </label>
  267. <label>
  268. Always on top
  269. <input type="checkbox" bind:checked={alwaysOnTop} />
  270. </label>
  271. <label>
  272. Content protected
  273. <input type="checkbox" bind:checked={contentProtected} />
  274. </label>
  275. <label>
  276. Fullscreen
  277. <input type="checkbox" bind:checked={fullscreen} />
  278. </label>
  279. </div>
  280. <br />
  281. <div class="flex flex-row gap-2 flex-wrap">
  282. <div class="flex children:grow flex-col">
  283. <div>
  284. X
  285. <input class="input" type="number" bind:value={x} min="0" />
  286. </div>
  287. <div>
  288. Y
  289. <input class="input" type="number" bind:value={y} min="0" />
  290. </div>
  291. </div>
  292. <div class="flex children:grow flex-col">
  293. <div>
  294. Width
  295. <input class="input" type="number" bind:value={width} min="400" />
  296. </div>
  297. <div>
  298. Height
  299. <input class="input" type="number" bind:value={height} min="400" />
  300. </div>
  301. </div>
  302. <div class="flex children:grow flex-col">
  303. <div>
  304. Min width
  305. <input class="input" type="number" bind:value={minWidth} />
  306. </div>
  307. <div>
  308. Min height
  309. <input class="input" type="number" bind:value={minHeight} />
  310. </div>
  311. </div>
  312. <div class="flex children:grow flex-col">
  313. <div>
  314. Max width
  315. <input class="input" type="number" bind:value={maxWidth} min="800" />
  316. </div>
  317. <div>
  318. Max height
  319. <input class="input" type="number" bind:value={maxHeight} min="400" />
  320. </div>
  321. </div>
  322. </div>
  323. <br />
  324. <div>
  325. <div class="flex">
  326. <div class="grow">
  327. <div class="text-accent dark:text-darkAccent font-700">
  328. Inner Size
  329. </div>
  330. <span>Width: {innerSize.width}</span>
  331. <span>Height: {innerSize.height}</span>
  332. </div>
  333. <div class="grow">
  334. <div class="text-accent dark:text-darkAccent font-700">
  335. Outer Size
  336. </div>
  337. <span>Width: {outerSize.width}</span>
  338. <span>Height: {outerSize.height}</span>
  339. </div>
  340. </div>
  341. <div class="flex">
  342. <div class="grow">
  343. <div class="text-accent dark:text-darkAccent font-700">
  344. Inner Logical Size
  345. </div>
  346. <span>Width: {innerSize.toLogical(scaleFactor).width}</span>
  347. <span>Height: {innerSize.toLogical(scaleFactor).height}</span>
  348. </div>
  349. <div class="grow">
  350. <div class="text-accent dark:text-darkAccent font-700">
  351. Outer Logical Size
  352. </div>
  353. <span>Width: {outerSize.toLogical(scaleFactor).width}</span>
  354. <span>Height: {outerSize.toLogical(scaleFactor).height}</span>
  355. </div>
  356. </div>
  357. <div class="flex">
  358. <div class="grow">
  359. <div class="text-accent dark:text-darkAccent font-700">
  360. Inner Position
  361. </div>
  362. <span>x: {innerPosition.x}</span>
  363. <span>y: {innerPosition.y}</span>
  364. </div>
  365. <div class="grow">
  366. <div class="text-accent dark:text-darkAccent font-700">
  367. Outer Position
  368. </div>
  369. <span>x: {outerPosition.x}</span>
  370. <span>y: {outerPosition.y}</span>
  371. </div>
  372. </div>
  373. <div class="flex">
  374. <div class="grow">
  375. <div class="text-accent dark:text-darkAccent font-700">
  376. Inner Logical Position
  377. </div>
  378. <span>x: {innerPosition.toLogical(scaleFactor).x}</span>
  379. <span>y: {innerPosition.toLogical(scaleFactor).y}</span>
  380. </div>
  381. <div class="grow">
  382. <div class="text-accent dark:text-darkAccent font-700">
  383. Outer Logical Position
  384. </div>
  385. <span>x: {outerPosition.toLogical(scaleFactor).x}</span>
  386. <span>y: {outerPosition.toLogical(scaleFactor).y}</span>
  387. </div>
  388. </div>
  389. </div>
  390. <br />
  391. <h4 class="mb-2">Cursor</h4>
  392. <div class="flex gap-2">
  393. <label>
  394. <input type="checkbox" bind:checked={cursorGrab} />
  395. Grab
  396. </label>
  397. <label>
  398. <input type="checkbox" bind:checked={cursorVisible} />
  399. Visible
  400. </label>
  401. <label>
  402. <input type="checkbox" bind:checked={cursorIgnoreEvents} />
  403. Ignore events
  404. </label>
  405. </div>
  406. <div class="flex gap-2">
  407. <label>
  408. Icon
  409. <select class="input" bind:value={cursorIcon}>
  410. {#each cursorIconOptions as kind}
  411. <option value={kind}>{kind}</option>
  412. {/each}
  413. </select>
  414. </label>
  415. <label>
  416. X position
  417. <input class="input" type="number" bind:value={cursorX} />
  418. </label>
  419. <label>
  420. Y position
  421. <input class="input" type="number" bind:value={cursorY} />
  422. </label>
  423. </div>
  424. <br />
  425. <div class="flex flex-col gap-1">
  426. <form class="flex gap-1" on:submit|preventDefault={setTitle_}>
  427. <input class="input grow" id="title" bind:value={windowTitle} />
  428. <button class="btn" type="submit">Set title</button>
  429. </form>
  430. <form class="flex gap-1" on:submit|preventDefault={openUrl}>
  431. <input class="input grow" id="url" bind:value={urlValue} />
  432. <button class="btn" id="open-url"> Open URL </button>
  433. </form>
  434. </div>
  435. {/if}
  436. </div>