diff --git a/AGENTS.md b/AGENTS.md index b1d11b6c..0d15e794 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -98,6 +98,7 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does; - 2026-10-14: Appearance shows every built-in theme as Ely's `RadioCard` in a `SimpleGrid`, each over a small window in the theme's colors. Separate dark theme copies the theme into `dark_theme`, so turning it on changes nothing until a dark card is picked. A theme change re-dresses the chrome at once. Font family comes from Ely's `FontPicker`; size, line height and padding from its `NumberInput`, which saves a number on Enter, on leaving the field and on each step. A font saves, and a settings file starts, only when gpui loads it as the terminal draws it: gpui falls back to another font without a word and loads none without an `m`, so the terminal draws with no font but its own. The cursor preview is a prompt in the terminal's font and theme, its cursor drawn as the terminal draws it, blinking on the terminal's beat. Opacity moves by whole percents, so the saved fraction carries no float noise. A change the store refuses leaves the setting as it was, and the page says why at its top; a number field drops its draft, and the font picker shows the saved family again. The settings window opens at 900 by 640, room for three theme columns. - 2026-10-14: Terminal shows the shell and where sessions start, which a General page held until the owner dropped it, then scrollback lines, Option as Alt, on macOS only, since elsewhere Alt is Alt, copy on select, the sessions column's drag and right press, and Ely, then every shortcut in Ely's `KeyboardShortcutsList`, read from the keymap: the bindings of each listed command that apply in the main window's terminal, matched at any depth of its context stack as gpui matches them, so the list cannot drift from the keys. The engine reads each when it needs it, so a change applies at once to every session; a smaller scrollback drops the history past it. - 2026-10-14: the main window lets the desktop through while Background opacity is under 1, blurred or clear as Background blur says, through gpui's `Blurred` (macOS's NSVisualEffectView, Windows' acrylic) or `Transparent`; at 1 it is `Opaque`. The workspace sets it as the settings change. Ely's `ColumnShell::glass`, added to Ely for this, grounds the columns in the palette's glass, which the opacity sets, so the title bar and every column let the desktop through alike; the palette's `bg` stays solid, so a dialog, the Git diff among them, hides what lies under it. The terminal paints no ground over the glass, or the two would stack; a program's own background (OSC 11) paints over it. Ely's own themes pass their palette too, Ely's colors with the glass set. The settings window stays opaque. +- 2026-11-03: Background image paints where Picture scope says: the terminal's column or the window below the title bar, never the title bar. `src/workspace/wallpaper.rs` paints the window's ground, the theme's background at the background opacity, and the picture over it at that opacity, once behind the workspace; a canvas ruler in the terminal column's body records those bounds, since a column's header may hold it wider than its width setting, and asks for another frame when they move. While a picture is set the columns' glass is 0, so the picture keeps its colors and the columns stand transparent over the ground; a program's own background (OSC 11) still paints over it. - 2026-10-15: on Wayland the window asks for `ext_background_effect_v1` beside gpui's `org_kde_kwin_blur`. A guest connection on gpui's own display binds it, takes the effect for the window's surface once, and blurs the window's pane: the surface less Ely's resize margin on each free edge of a client-drawn window, worked out each frame and sent only when it changes; it goes and comes back with Blurred and lands at the surface's next commit, which every frame makes. The guest connection reads the display only while the window opens, before gpui's loop runs, and is never dropped: a drop destroys its proxies through the display, which gpui may close first. The log names what the compositor offers, so which way blur goes: `ext_background_effect_v1`, gpui's `org_kde_kwin_blur`, or none, the desktop clear. Its test runs a `wayland-server` compositor on a socket pair. - 2026-10-15: on a client-drawn window, so on Linux, Ely's `ResizeBorder` paints its pane solid and shadowed under the content, which kept the desktop from showing at all. `ResizeBorder::glass`, added to Ely for this, leaves the pane to the content while the window is see-through: no ground, nor the shadow gpui draws under the box, which would tint it. So the blur covers the pane alone and the resize margin stays clear. On X11 the window names its pane in `_KDE_NET_WM_BLUR_BEHIND_REGION` (CARDINAL, 32: x, y, width, height in the window's pixels), as Ghostty names its own, and drops the property when Blurred goes; KWin reads it, other compositors pass it by. It goes over a connection of ELYTerminal's own to `$DISPLAY`, which gpui opens too: a property belongs to the window, whoever sets it. gpui's own `org_kde_kwin_blur` on KDE's Wayland still covers the whole surface, margin included, beyond ELYTerminal's reach. - 2026-10-15: the macOS menu bar is the PRD's: ELYTerminal (Settings…, Quit), Shell (New Session, Close Session), Edit (Copy, Paste, Select All, Find), View (Toggle Sessions, Toggle Right Panel, Bigger, Smaller, Reset Size), Window (Minimize, Zoom, then the windows macOS lists). An item shows the key bound to its action, so the menus are set after the bindings; Copy, Paste and Select All go through AppKit's `copy:`, `paste:` and `selectAll:`, as gpui's `OsAction` maps them. They are Ely's text input actions, which `forms` exports for this, and the terminal handles them too: gpui sends a menu's action to what holds focus and enables the item only where a handler waits, so one action serves the terminal, find and every field. The new keys are Ghostty's: Cmd-Q, Cmd-A, Cmd-= and Cmd-+, Cmd-minus, Cmd-0, Cmd-M. Off macOS Quit and Select All take Ctrl-Shift, since Ctrl with a letter types a control byte; the font's size takes plain Ctrl with =, +, minus and 0, which type none, as in Ghostty, Alacritty and Windows Terminal; Minimize has no key there. Bigger and Smaller move every session's font a point, inside the Size range, and Reset Size goes back to the setting; nothing saves the change, as in Ghostty. Select All runs from the first character in the history to the last on the screen, the blanks around them left out, as Ghostty's, though a space under a combining mark shows a character and counts as one; copy on select takes it, and a blank grid selects nothing. diff --git a/PRD.md b/PRD.md index 52610439..48900ba7 100644 --- a/PRD.md +++ b/PRD.md @@ -129,7 +129,7 @@ Cmd-, opens it; on Windows and Linux, which have no menu bar, so does a gear in - Separate dark theme: a switch; when on, a second grid for dark mode. - Font family: a list of installed fonts. Fallbacks: installed families tried in order for a character the family lacks, each added, changed or removed in a list of pickers. CJK font: a family Chinese, Japanese and Korean characters draw in, with their marks and variation selectors, before Family, empty by default, cleared by Clear. Font size. Weight, 100 to 900, 400 by default, normal text's; bold sits 300 above it, up to 900. Line height. Ligatures. Antialiasing, on Windows and Linux: Grayscale, as Windows Terminal draws, by default, or Subpixel, which gpui draws only on an opaque window. - Cursor: style (block, bar, underline, hollow), blink, glide, on by default, which slides the cursor to its new cell in 90 ms, with a live preview. - - Background opacity: a slider. Background image: a picture under the terminal, at that opacity, chosen as a file. Background blur: a switch. Backdrop, on Windows 11 from build 22621: Acrylic, Mica or Mica Alt. + - Background opacity: a slider. Background image: a picture under the terminal or the window, at that opacity, chosen as a file. Picture scope: the terminal's column, or the window below the title bar. Background blur: a switch. Backdrop, on Windows 11 from build 22621: Acrylic, Mica or Mica Alt. - Padding: horizontal and vertical. - Terminal - Shell: a path; empty runs the login shell, on Windows `pwsh`, else `powershell`, else `cmd`. On Windows a session takes the registry's environment as it starts. diff --git a/docs/en_us/features.md b/docs/en_us/features.md index 22d9d814..67902120 100644 --- a/docs/en_us/features.md +++ b/docs/en_us/features.md @@ -24,3 +24,4 @@ English / [中文](../zh_cn/features.md) - Interface font and size in Settings › Appearance: any installed family, with a sample line, and 12 to 16 px for every window's chrome; the terminal keeps its own font. - On Windows, Settings › Terminal › Explorer menu adds Open in ELYTerminal to a folder's right-click menu, for you alone, and removes it; on Windows 11 it sits under Show more options. `elyterminal FOLDER` starts the first session in FOLDER on every system; cmd, which refuses a network share as its folder, says so and starts in the Windows folder. The folder may also follow `--working-directory`, `-d` or `--startingDirectory`, as xdg-terminal-exec and Windows Terminal pass it. - Background opacity and blur: NSVisualEffectView on macOS, acrylic on Windows, or on Windows 11 from build 22621 Mica or Mica Alt, as Settings › Appearance › Backdrop says, `ext_background_effect_v1` or `org_kde_kwin_blur` on Wayland, `_KDE_NET_WM_BLUR_BEHIND_REGION` on X11, which KWin reads. Blur shows only while the opacity is under 100%. Hyprland blurs only while its own `decoration:blur:enabled` is on, and Omarchy 4.0.4 turns it off; to turn it on there, add `hl.config({ decoration = { blur = { enabled = true } } })` to `~/.config/hypr/looknfeel.lua`. +- Background image: a picture at Settings › Appearance › Background image, drawn at the background's opacity where Picture scope says, under the terminal's column or under the whole window below the title bar. A column's header can hold the terminal wider than its width setting, and the picture follows the terminal itself. The picture keeps its colors: while one is set, the columns' glass is 0, so a window-wide picture shows through them. diff --git a/docs/zh_cn/features.md b/docs/zh_cn/features.md index be76601c..4106ba27 100644 --- a/docs/zh_cn/features.md +++ b/docs/zh_cn/features.md @@ -24,3 +24,4 @@ - 设置 › 外观 中的界面字体和字号:任一已安装字体,附示例行,12 到 16 px,作用于每个窗口的界面;终端保留自己的字体。 - Windows 上,设置 › 终端 › 资源管理器菜单 可为当前用户在文件夹的右键菜单中加入或移除“在 ELYTerminal 中打开”;Windows 11 中它位于“显示更多选项”下。`elyterminal FOLDER` 在各系统上都让第一个会话从 FOLDER 开始;cmd 不接受网络共享作为当前目录,会自行提示并进入 Windows 目录。文件夹也可以写在 `--working-directory`、`-d` 或 `--startingDirectory` 之后,与 xdg-terminal-exec 和 Windows Terminal 的写法相同。 - 背景不透明度和模糊:macOS 上是 NSVisualEffectView,Windows 上是 acrylic,Windows 11 build 22621 起可按设置 › 外观 › 背景材质选云母或云母 Alt,Wayland 上是 `ext_background_effect_v1` 或 `org_kde_kwin_blur`,X11 上是 `_KDE_NET_WM_BLUR_BEHIND_REGION`,KWin 会读它。模糊只在不透明度低于 100% 时出现。Hyprland 只在自己的 `decoration:blur:enabled` 打开时模糊,而 Omarchy 4.0.4 把它关掉了;要在那里打开,把 `hl.config({ decoration = { blur = { enabled = true } } })` 加到 `~/.config/hypr/looknfeel.lua`。 +- 背景图片:在设置 › 外观 › 背景图片 选一张图,按背景不透明度显示,位置由 图片范围 决定:终端列下方,或标题栏以下的整个窗口。列头可能让终端比宽度设置更宽,图片跟着终端本身。图片保持原色:设置图片时各列的玻璃为 0,整窗图片会从列中透出。 diff --git a/src/app.rs b/src/app.rs index 07212475..17f4f5dc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -102,6 +102,7 @@ pub fn start(cx: &mut App) -> anyhow::Result<()> { theme, settings.custom_themes.clone(), settings.background_opacity, + !settings.background_image.is_empty(), ) }; let mut image = SettingsStore::get(cx).background_image.clone(); diff --git a/src/i18n/en.rs b/src/i18n/en.rs index 3592e204..4f307a5d 100644 --- a/src/i18n/en.rs +++ b/src/i18n/en.rs @@ -94,8 +94,11 @@ pub const MESSAGES: &[(&str, &str)] = &[ ("et.appearance.image", "Background image"), ( "et.appearance.image_hint", - "A picture under the terminal, at the background's opacity", + "A picture under the terminal or the window, at the background's opacity", ), + ("et.appearance.scope", "Picture scope"), + ("et.appearance.scope_terminal", "Terminal"), + ("et.appearance.scope_window", "Window"), ("et.custom.new", "New theme"), ("et.custom.name_of", "Custom {number}"), ("et.custom.name", "Name"), diff --git a/src/i18n/zh.rs b/src/i18n/zh.rs index 297b9ef4..39ac76c8 100644 --- a/src/i18n/zh.rs +++ b/src/i18n/zh.rs @@ -91,8 +91,11 @@ pub const MESSAGES: &[(&str, &str)] = &[ ("et.appearance.image", "背景图片"), ( "et.appearance.image_hint", - "终端下方的图片,按背景不透明度显示", + "终端或窗口下方的图片,按背景不透明度显示", ), + ("et.appearance.scope", "图片范围"), + ("et.appearance.scope_terminal", "终端"), + ("et.appearance.scope_window", "窗口"), ("et.custom.new", "新建主题"), ("et.custom.name_of", "自定义 {number}"), ("et.custom.name", "名称"), diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 16dc237b..f4e2aa20 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -95,6 +95,15 @@ pub enum PanelTab { Files, } +/// Where the background picture paints: the terminal's column or the window below the title bar. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum BackgroundScope { + #[default] + Terminal, + Window, +} + /// Everything the settings window edits. A field the file leaves out takes its default. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] @@ -118,6 +127,8 @@ pub struct Settings { pub custom_themes: Vec, /// A picture under the terminal; empty for none. pub background_image: String, + /// Where the picture paints: the terminal's column or the window below the title bar. + pub background_scope: BackgroundScope, pub font_family: String, /// Families tried in order for a character Font family lacks. pub font_fallbacks: Vec, @@ -193,6 +204,7 @@ impl Default for Settings { dark_theme: None, custom_themes: Vec::new(), background_image: String::new(), + background_scope: BackgroundScope::Terminal, font_family: "JetBrains Mono".into(), font_fallbacks: Vec::new(), cjk_font: String::new(), diff --git a/src/settings/tests.rs b/src/settings/tests.rs index a74ef77c..45d2b5ca 100644 --- a/src/settings/tests.rs +++ b/src/settings/tests.rs @@ -361,3 +361,14 @@ fn the_interface_wears_the_system_font_at_13_px_unless_named() { ); } } + +#[test] +fn a_picture_lies_under_the_terminal_unless_the_window_is_chosen() { + let settings: Settings = serde_json::from_str("{}").expect("every field has a default"); + assert_eq!(settings.background_scope, BackgroundScope::Terminal); + let window: Settings = + serde_json::from_str(r#"{"background_scope":"window"}"#).expect("a scope"); + assert_eq!(window.background_scope, BackgroundScope::Window); + let json = serde_json::to_string(&window).expect("the settings write"); + assert!(json.contains(r#""background_scope":"window""#), "{json}"); +} diff --git a/src/settings/window/appearance.rs b/src/settings/window/appearance.rs index de08c925..b4bad8b3 100644 --- a/src/settings/window/appearance.rs +++ b/src/settings/window/appearance.rs @@ -26,8 +26,8 @@ use super::{ use crate::{ blur, settings::{ - Backdrop, FONT_SIZE, FONT_WEIGHT, LINE_HEIGHT, Language, OPACITY, PADDING, Settings, - SettingsStore, + Backdrop, BackgroundScope, FONT_SIZE, FONT_WEIGHT, LINE_HEIGHT, Language, OPACITY, PADDING, + Settings, SettingsStore, }, }; @@ -44,6 +44,20 @@ const LANGUAGES: [(Language, &str, &str); 2] = [ (Language::SimplifiedChinese, "zh-CN", "et.language.chinese"), ]; +/// Each picture scope, its key and its name's key. +const SCOPES: [(BackgroundScope, &str, &str); 2] = [ + ( + BackgroundScope::Terminal, + "terminal", + "et.appearance.scope_terminal", + ), + ( + BackgroundScope::Window, + "window", + "et.appearance.scope_window", + ), +]; + impl SettingsWindow { /// The language, theme, font, cursor and window rows. pub(super) fn appearance(&self, window: &Window, cx: &Context) -> Div { @@ -194,6 +208,16 @@ impl SettingsWindow { .description(word("et.appearance.image_hint")) .control(image), ) + .row( + SettingsRow::new(word("et.appearance.scope")).control(choice( + &this, + "background_scope", + &SCOPES, + settings.background_scope, + |settings, scope| settings.background_scope = scope, + cx, + )), + ) .row(SettingsRow::new(word("et.appearance.blur")).control(switch( &this, "background_blur", diff --git a/src/term/backdrop.rs b/src/term/backdrop.rs deleted file mode 100644 index 9e22cb83..00000000 --- a/src/term/backdrop.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! The picture under the terminal, as the settings name it. - -use std::{path::Path, sync::Arc}; - -use gpui::{ - AnyElement, Context, ImageAssetLoader, IntoElement, ObjectFit, Resource, Styled, StyledImage, - Window, img, -}; - -use super::view::TerminalView; -use crate::settings::Settings; - -impl TerminalView { - /// The picture at the background's opacity; a failed load logs once. - pub(super) fn backdrop( - &mut self, - settings: &Settings, - window: &mut Window, - cx: &mut Context, - ) -> Option { - if settings.background_image.is_empty() { - return None; - } - let path: Arc = Path::new(&settings.background_image).into(); - let source = Resource::Path(path.clone()); - match window.use_asset::(&source, cx)? { - Ok(image) => Some( - img(image) - .absolute() - .size_full() - .object_fit(ObjectFit::Cover) - .opacity(settings.background_opacity) - .into_any_element(), - ), - Err(error) => { - if self.backdrop_failed.as_deref() != Some(&*path) { - log::error!( - "terminal {}: background image {}: {error}", - self.pty.pid, - path.display() - ); - self.backdrop_failed = Some(path.to_path_buf()); - } - None - } - } - } -} diff --git a/src/term/mod.rs b/src/term/mod.rs index 7d22dea4..f9b5c93a 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -1,4 +1,3 @@ -mod backdrop; mod boxes; mod click; mod clipboard; diff --git a/src/term/view.rs b/src/term/view.rs index ef20c1fb..91f58529 100644 --- a/src/term/view.rs +++ b/src/term/view.rs @@ -81,8 +81,6 @@ pub struct TerminalView { pub(super) pointer: Pointer, /// The mouse mode, as the tap follows it. pub(super) mouse: MouseMode, - /// The background picture that last failed, logged once. - pub(super) backdrop_failed: Option, /// Kitty graphics' textures. pictures: Pictures, /// Keys go as Windows' records, as ConPTY asks. @@ -169,7 +167,6 @@ impl TerminalView { target: None, pointer: Pointer::default(), mouse: MouseMode::default(), - backdrop_failed: None, pictures: Pictures::default(), #[cfg(windows)] win32: false, @@ -452,11 +449,9 @@ impl Render for TerminalView { root.text_color(ink.fg).child(format!("{error:#}")) } }; - let backdrop = self.backdrop(&settings, window, cx); div() .relative() .size_full() - .children(backdrop) .child(root) .children(scrollbar::bar(span, window, cx)) .children( diff --git a/src/themes/chrome.rs b/src/themes/chrome.rs index 90cbd286..249edfb9 100644 --- a/src/themes/chrome.rs +++ b/src/themes/chrome.rs @@ -77,7 +77,12 @@ pub fn palette(theme: &TerminalTheme, opacity: f32) -> Palette { pub fn apply(cx: &mut App) { let settings = SettingsStore::get(cx); let theme = active(settings, cx.window_appearance(), cx); - let opacity = settings.background_opacity; + // A picture grounds the window; the columns leave it as it is. + let opacity = if settings.background_image.is_empty() { + settings.background_opacity + } else { + 0.0 + }; let mode = if theme.is_dark() { Mode::Dark } else { diff --git a/src/workspace.rs b/src/workspace.rs index bc529ca7..556bf6fd 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -7,7 +7,7 @@ use ely_gpui_component::{ motion::Reorder, primitives::{FocusScope, IconName}, shell::{ColumnShell, ResizeBorder, ShellColumn}, - theme::ControlSize, + theme::{ActiveTheme, ControlSize}, typography::Caption, }; use gpui::{ @@ -33,6 +33,7 @@ mod rename; mod reorder; mod session; mod toggles; +mod wallpaper; mod widths; use toggles::column_toggle; @@ -313,7 +314,8 @@ impl Render for Workspace { self.background.fit(window); let now = Instant::now(); self.tick(now, cx); - let settings = SettingsStore::get(cx); + let settings = SettingsStore::get(cx).clone(); + let base = cx.theme().colors.bg; let workspace = cx.entity(); // It leads the first column or ends the last. let left = column_toggle( @@ -357,7 +359,13 @@ impl Render for Workspace { .map(|session| session.terminal.clone()); let mut terminal = ShellColumn::new("terminal") .title(self.title().to_string()) - .children(shown); + .child( + div() + .relative() + .size_full() + .children(shown) + .child(wallpaper::ruler()), + ); if settings.show_sessions { let home = dirs::home_dir(); let rows: Vec<(String, AnyElement)> = self @@ -487,7 +495,15 @@ impl Render for Workspace { .child( ResizeBorder::new() .when(self.background.see_through(), ResizeBorder::glass) - .child(FocusScope::new(&self.focus).root().size_full().child(shell)), + .child( + FocusScope::new(&self.focus).root().size_full().child( + div() + .relative() + .size_full() + .children(wallpaper::paint(&settings, base, window, cx)) + .child(shell), + ), + ), ) } } diff --git a/src/workspace/wallpaper.rs b/src/workspace/wallpaper.rs new file mode 100644 index 00000000..f51740c5 --- /dev/null +++ b/src/workspace/wallpaper.rs @@ -0,0 +1,128 @@ +//! The background picture, where the settings put it. + +use std::{ + cell::{Cell, RefCell}, + path::{Path, PathBuf}, + sync::Arc, +}; + +use gpui::{ + AnyElement, App, Bounds, Hsla, ImageAssetLoader, IntoElement, ObjectFit, ParentElement, Pixels, + Resource, Size, Styled, StyledImage, Window, canvas, div, img, point, px, size, +}; + +use crate::settings::{BackgroundScope, Settings}; + +thread_local! { + /// The terminal body's bounds, as the last frame laid them. + static TERMINAL: Cell>> = const { Cell::new(None) }; + /// The picture whose failure was logged last, so each frame does not log it again. + static FAILED: RefCell> = const { RefCell::new(None) }; +} + +/// The picture's bounds: the terminal body, or the window below the title bar. +fn box_of(scope: BackgroundScope, body: Bounds, viewport: Size) -> Bounds { + match scope { + BackgroundScope::Terminal => body, + BackgroundScope::Window => Bounds::new( + point(px(0.0), body.origin.y), + size(viewport.width, viewport.height - body.origin.y), + ), + } +} + +/// A canvas that records the terminal body's bounds, so the window's picture starts below the title bar. +pub(super) fn ruler() -> impl IntoElement { + canvas( + |bounds, window, _: &mut App| { + if TERMINAL.get() != Some(bounds) { + TERMINAL.set(Some(bounds)); + window.request_animation_frame(); + } + }, + |_, (), _, _| {}, + ) + .absolute() + .inset_0() +} + +/// The window's ground and the picture over it, at the background's opacity; nothing without one. +pub(super) fn paint( + settings: &Settings, + base: Hsla, + window: &mut Window, + cx: &mut App, +) -> Option { + if settings.background_image.is_empty() { + return None; + } + let ground = div() + .absolute() + .inset_0() + .opacity(settings.background_opacity) + .child(div().absolute().inset_0().bg(base)); + let Some(body) = TERMINAL.get() else { + return Some(ground.into_any_element()); + }; + let bounds = box_of(settings.background_scope, body, window.viewport_size()); + let path: Arc = Path::new(&settings.background_image).into(); + let source = Resource::Path(path.clone()); + let Some(loaded) = window.use_asset::(&source, cx) else { + return Some(ground.into_any_element()); + }; + match loaded { + Ok(image) => Some( + ground + .child( + img(image) + .absolute() + .left(bounds.origin.x) + .top(bounds.origin.y) + .w(bounds.size.width) + .h(bounds.size.height) + .object_fit(ObjectFit::Cover), + ) + .into_any_element(), + ), + Err(error) => { + let fresh = FAILED.with(|failed| { + let mut failed = failed.borrow_mut(); + if failed.as_deref() == Some(&*path) { + return false; + } + *failed = Some(path.to_path_buf()); + true + }); + if fresh { + log::error!("background: {}: {error}", path.display()); + } + Some(ground.into_any_element()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn body() -> Bounds { + Bounds::new(point(px(220.0), px(38.0)), size(px(800.0), px(600.0))) + } + + #[test] + fn the_terminal_scope_paints_the_terminal_body() { + let bounds = box_of( + BackgroundScope::Terminal, + body(), + size(px(1200.0), px(700.0)), + ); + assert_eq!(bounds, body()); + } + + #[test] + fn the_window_scope_starts_below_the_title_bar_and_spans_the_window() { + let bounds = box_of(BackgroundScope::Window, body(), size(px(1200.0), px(700.0))); + assert_eq!(bounds.origin, point(px(0.0), px(38.0))); + assert_eq!(bounds.size, size(px(1200.0), px(662.0))); + } +} diff --git a/tasks/acceptance.md b/tasks/acceptance.md index 320ecfa7..f3d3e8e3 100644 --- a/tasks/acceptance.md +++ b/tasks/acceptance.md @@ -13,7 +13,7 @@ What no check reached: issue #29's physical keyboard and fcitx5's own forwarding | Silent: no sound, bell, notification but a session's or a newer release's, telemetry, tip or onboarding | T10, T12, T40, T56 | a bell is logged and marks a hidden row; the one network call is the update check the owner asked for in T56, `curl` to GitHub's `releases/latest` at start and every 24 hours a run lasts while Check for updates is on, and when the switch turns on or Check now is pressed, neither of which posts; the notifications are a session's and, at each automatic check that finds one, a newer release's, under its release's tag, through gpui's, which sets no sound on macOS and a silent toast on Windows; on Linux the notification daemon decides | Pass | | Clean: no subtitle, tagline, logging option, status bar or badge without state | T11–T18 | reviews of the layout and settings tasks | Pass | | Built-in themes, each with a preview, Catppuccin among them; a theme colors terminal and chrome | T03, T17 | `themes::tests`; T17 review | Pass | -| The user's own themes from Ely Dark: New theme, their name and 21 colors, Delete; built-ins never change; a background image under the terminal | T109 | `themes::custom` tests: Ely Dark's copy under the next id, each slot's color, a color, a prefix or a name that does not read names the theme; `settings` tests: a custom id chosen, one gone or held twice refused, a background image a file at a full path; on macOS `shot`: New theme made and wore `custom-1`, the picker saved a foreground and the chrome wore it, the name saved at each key, Delete wore Ely Dark; a PNG drew under the text, a script named as one logged one error | Pass | +| The user's own themes from Ely Dark: New theme, their name and 21 colors, Delete; built-ins never change; a background image under the terminal or the window, as Picture scope says | T109 | `themes::custom` tests: Ely Dark's copy under the next id, each slot's color, a color, a prefix or a name that does not read names the theme; `settings` tests: a custom id chosen, one gone or held twice refused, a background image a file at a full path, its scope the terminal unless the window is chosen; on macOS `shot`: New theme made and wore `custom-1`, the picker saved a foreground and the chrome wore it, the name saved at each key, Delete wore Ely Dark; a PNG drew under the text, a script named as one logged one error | Pass | | The background blurs on macOS, Windows, Wayland and X11 | T19–T21 | see §11 | Partial | ## 2. Foundation