From be82b67ed3eaf877bf0d270a532402d2263f8975 Mon Sep 17 00:00:00 2001 From: Desperate Necromancer <57827456+AlphaNecron@users.noreply.github.com> Date: Sat, 17 May 2025 03:33:59 +0700 Subject: [PATCH] Allow disabling window decorations/controls (#176) Co-authored-by: Gregory Schier --- .../20250302041707_hide-window-controls.sql | 2 + src-tauri/yaak-models/bindings/gen_models.ts | 2 +- src-tauri/yaak-models/bindings/gen_util.ts | 12 ++--- src-tauri/yaak-models/src/models.rs | 4 ++ src-tauri/yaak-models/src/queries/settings.rs | 1 + src-web/components/HeaderSize.tsx | 46 ++++++++++++------- src-web/components/Settings/Settings.tsx | 7 +-- .../Settings/SettingsAppearance.tsx | 10 ++++ src-web/components/WindowControls.tsx | 13 +++--- src-web/components/core/Checkbox.tsx | 4 +- src-web/components/core/HotKey.tsx | 4 +- src-web/components/core/Select.tsx | 5 +- src-web/hooks/useHotKey.ts | 6 +-- src-web/hooks/useOsInfo.ts | 5 -- src-web/hooks/useStoplightsVisible.ts | 5 +- src-web/lib/constants.ts | 2 +- src-web/routes/__root.tsx | 5 +- 17 files changed, 75 insertions(+), 58 deletions(-) create mode 100644 src-tauri/migrations/20250302041707_hide-window-controls.sql delete mode 100644 src-web/hooks/useOsInfo.ts diff --git a/src-tauri/migrations/20250302041707_hide-window-controls.sql b/src-tauri/migrations/20250302041707_hide-window-controls.sql new file mode 100644 index 00000000..fb2017f4 --- /dev/null +++ b/src-tauri/migrations/20250302041707_hide-window-controls.sql @@ -0,0 +1,2 @@ +ALTER TABLE settings + ADD COLUMN hide_window_controls BOOLEAN DEFAULT FALSE NOT NULL; diff --git a/src-tauri/yaak-models/bindings/gen_models.ts b/src-tauri/yaak-models/bindings/gen_models.ts index e59ed39f..7e5e0e60 100644 --- a/src-tauri/yaak-models/bindings/gen_models.ts +++ b/src-tauri/yaak-models/bindings/gen_models.ts @@ -58,7 +58,7 @@ export type ProxySetting = { "type": "enabled", disabled: boolean, http: string, export type ProxySettingAuth = { user: string, password: string, }; -export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, }; +export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, }; export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, }; diff --git a/src-tauri/yaak-models/bindings/gen_util.ts b/src-tauri/yaak-models/bindings/gen_util.ts index a38424de..482d1902 100644 --- a/src-tauri/yaak-models/bindings/gen_util.ts +++ b/src-tauri/yaak-models/bindings/gen_util.ts @@ -1,9 +1,9 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Environment } from "./gen_models"; -import type { Folder } from "./gen_models"; -import type { GrpcRequest } from "./gen_models"; -import type { HttpRequest } from "./gen_models"; -import type { WebsocketRequest } from "./gen_models"; -import type { Workspace } from "./gen_models"; +import type { Environment } from "./gen_models.js"; +import type { Folder } from "./gen_models.js"; +import type { GrpcRequest } from "./gen_models.js"; +import type { HttpRequest } from "./gen_models.js"; +import type { WebsocketRequest } from "./gen_models.js"; +import type { Workspace } from "./gen_models.js"; export type BatchUpsertResult = { workspaces: Array, environments: Array, folders: Array, httpRequests: Array, grpcRequests: Array, websocketRequests: Array, }; diff --git a/src-tauri/yaak-models/src/models.rs b/src-tauri/yaak-models/src/models.rs index 0a3f1820..7a5f24ef 100644 --- a/src-tauri/yaak-models/src/models.rs +++ b/src-tauri/yaak-models/src/models.rs @@ -105,6 +105,7 @@ pub struct Settings { pub appearance: String, pub editor_font_size: i32, pub editor_soft_wrap: bool, + pub hide_window_controls: bool, pub interface_font_size: i32, pub interface_scale: f32, pub open_workspace_new_window: Option, @@ -154,6 +155,7 @@ impl UpsertModelInfo for Settings { (EditorSoftWrap, self.editor_soft_wrap.into()), (InterfaceFontSize, self.interface_font_size.into()), (InterfaceScale, self.interface_scale.into()), + (HideWindowControls, self.hide_window_controls.into()), (OpenWorkspaceNewWindow, self.open_workspace_new_window.into()), (ThemeDark, self.theme_dark.as_str().into()), (ThemeLight, self.theme_light.as_str().into()), @@ -171,6 +173,7 @@ impl UpsertModelInfo for Settings { SettingsIden::EditorSoftWrap, SettingsIden::InterfaceFontSize, SettingsIden::InterfaceScale, + SettingsIden::HideWindowControls, SettingsIden::OpenWorkspaceNewWindow, SettingsIden::Proxy, SettingsIden::ThemeDark, @@ -200,6 +203,7 @@ impl UpsertModelInfo for Settings { proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }), theme_dark: row.get("theme_dark")?, theme_light: row.get("theme_light")?, + hide_window_controls: row.get("hide_window_controls")?, update_channel: row.get("update_channel")?, }) } diff --git a/src-tauri/yaak-models/src/queries/settings.rs b/src-tauri/yaak-models/src/queries/settings.rs index 2bc7504a..5635abd5 100644 --- a/src-tauri/yaak-models/src/queries/settings.rs +++ b/src-tauri/yaak-models/src/queries/settings.rs @@ -23,6 +23,7 @@ impl<'a> DbContext<'a> { editor_soft_wrap: true, interface_font_size: 15, interface_scale: 1.0, + hide_window_controls: false, open_workspace_new_window: None, proxy: None, theme_dark: "yaak-dark".to_string(), diff --git a/src-web/components/HeaderSize.tsx b/src-web/components/HeaderSize.tsx index e3eb6cc0..0aa769fd 100644 --- a/src-web/components/HeaderSize.tsx +++ b/src-web/components/HeaderSize.tsx @@ -1,9 +1,8 @@ import { settingsAtom } from '@yaakapp-internal/models'; import classNames from 'classnames'; import { useAtomValue } from 'jotai'; -import type { HTMLAttributes, ReactNode } from 'react'; -import React from 'react'; -import { useOsInfo } from '../hooks/useOsInfo'; +import type { CSSProperties, HTMLAttributes, ReactNode } from 'react'; +import React, { useMemo } from 'react'; import { useStoplightsVisible } from '../hooks/useStoplightsVisible'; import { HEADER_SIZE_LG, HEADER_SIZE_MD, WINDOW_CONTROLS_WIDTH } from '../lib/constants'; import { WindowControls } from './WindowControls'; @@ -23,27 +22,42 @@ export function HeaderSize({ onlyXWindowControl, children, }: HeaderSizeProps) { - const osInfo = useOsInfo(); const settings = useAtomValue(settingsAtom); const stoplightsVisible = useStoplightsVisible(); + const finalStyle = useMemo(() => { + const s = { ...style }; + + // Set the height (use min-height because scaling font size may make it larger + if (size === 'md') s.minHeight = HEADER_SIZE_MD; + if (size === 'lg') s.minHeight = HEADER_SIZE_LG; + + // Add large padding for window controls + if (stoplightsVisible && !ignoreControlsSpacing) { + s.paddingLeft = 72 / settings.interfaceScale; + } else if (!stoplightsVisible && !ignoreControlsSpacing && !settings.hideWindowControls) { + s.paddingRight = WINDOW_CONTROLS_WIDTH; + } + + return s; + }, [ + ignoreControlsSpacing, + settings.hideWindowControls, + settings.interfaceScale, + size, + stoplightsVisible, + style, + ]); + return (
{/* NOTE: This needs display:grid or else the element shrinks (even though scrollable) */} diff --git a/src-web/components/Settings/Settings.tsx b/src-web/components/Settings/Settings.tsx index 9b5208a7..21d1888c 100644 --- a/src-web/components/Settings/Settings.tsx +++ b/src-web/components/Settings/Settings.tsx @@ -1,9 +1,9 @@ import { useSearch } from '@tanstack/react-router'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; +import { type } from '@tauri-apps/plugin-os'; import classNames from 'classnames'; import React, { useState } from 'react'; import { useKeyPressEvent } from 'react-use'; -import { useOsInfo } from '../../hooks/useOsInfo'; import { capitalize } from '../../lib/capitalize'; import { HStack } from '../core/Stacks'; import { TabContent, Tabs } from '../core/Tabs/Tabs'; @@ -27,7 +27,6 @@ const tabs = [TAB_GENERAL, TAB_APPEARANCE, TAB_PROXY, TAB_PLUGINS, TAB_LICENSE] export type SettingsTab = (typeof tabs)[number]; export default function Settings({ hide }: Props) { - const osInfo = useOsInfo(); const { tab: tabFromQuery } = useSearch({ from: '/workspaces/$workspaceId/settings' }); const [tab, setTab] = useState(tabFromQuery); @@ -60,9 +59,7 @@ export default function Settings({ hide }: Props) { justifyContent="center" className="w-full h-full grid grid-cols-[1fr_auto] pointer-events-none" > -
- Settings -
+
Settings
)} diff --git a/src-web/components/Settings/SettingsAppearance.tsx b/src-web/components/Settings/SettingsAppearance.tsx index dbff9833..9fd37a07 100644 --- a/src-web/components/Settings/SettingsAppearance.tsx +++ b/src-web/components/Settings/SettingsAppearance.tsx @@ -18,6 +18,7 @@ import type { SelectProps } from '../core/Select'; import { Select } from '../core/Select'; import { Separator } from '../core/Separator'; import { HStack, VStack } from '../core/Stacks'; +import { type } from '@tauri-apps/plugin-os'; const fontSizeOptions = [ 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, @@ -122,6 +123,15 @@ export function SettingsAppearance() { onChange={(editorSoftWrap) => patchModel(settings, { editorSoftWrap })} /> + {type() !== 'macos' && ( + patchModel(settings, { hideWindowControls })} + /> + )} + { name: string; @@ -40,7 +40,6 @@ export function Select({ defaultValue, size = 'md', }: SelectProps) { - const osInfo = useOsInfo(); const [focused, setFocused] = useState(false); const id = `input-${name}`; const isInvalidSelection = options.find((o) => 'value' in o && o.value === value) == null; @@ -63,7 +62,7 @@ export function Select({ - {osInfo?.osType === 'macos' ? ( + {type() === 'macos' ? ( @@ -36,7 +35,7 @@ function RouteComponent() {