mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 00:02:23 +00:00
fix(theme): review follow-ups — diff overlay background, hot-reload window effects, docs
- Diff overlay paints the gradient/opacity-aware window background instead of the stale representative solid. - Config hot-reload now re-runs apply_theme with the window (blur flip, traffic-light re-pin) and re-syncs the Appearance opacity slider, so hand-edits to config.json / theme files take effect fully. - Document the theme background/window settings in features docs (EN + zh-CN).
This commit is contained in:
+3
-1
@@ -17,7 +17,9 @@
|
||||
- **Tabs & splits** — always open in the current directory
|
||||
- **Command palette** <kbd>⌘ P</kbd> · scrollback search <kbd>⌘ F</kbd>
|
||||
- **⌘-click links** · desktop notifications · copy on select (opt-in, Settings → Terminal → Clipboard)
|
||||
- **Eight themes** · CJK / IME input
|
||||
- **Eight themes, plus your own** — YAML seed themes with solid, gradient, or image backgrounds; iTerm2 `.itermcolors` import; in-app color editor with a background-image picker
|
||||
- **Window opacity & blur** — Settings → Appearance → Window; applies to every theme, *Follow theme* returns to the theme's own `opacity` / `blur`
|
||||
- **CJK / IME input**
|
||||
|
||||
## Coding agents
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
- **标签页与分屏** —— 永远开在当前目录
|
||||
- **命令面板** <kbd>⌘ P</kbd> · 回滚搜索 <kbd>⌘ F</kbd>
|
||||
- **⌘ 点击打开链接** · 桌面通知 · 划选即复制(可选,设置 → 终端 → 剪贴板)
|
||||
- **8 套主题** · CJK / 输入法输入
|
||||
- **8 套主题,也能自定义** — YAML 种子主题,背景支持纯色、渐变或图片;可导入 iTerm2 `.itermcolors`;应用内颜色编辑器带背景图选择
|
||||
- **窗口透明与模糊** — 设置 → Appearance → Window;对所有主题生效,*Follow theme* 恢复主题自带的 `opacity` / `blur`
|
||||
- **CJK / 输入法输入**
|
||||
|
||||
## Coding agent
|
||||
|
||||
|
||||
+5
-4
@@ -137,8 +137,10 @@ fn spawn_config_watcher(cx: &mut App) {
|
||||
// Re-paint theme + colors from the new config. We have no window
|
||||
// handle in this global task, but `apply_theme` accepts `None`:
|
||||
// it still updates the `Theme`/palette globals (what actually
|
||||
// repaints); the only thing it skips is re-pinning the macOS
|
||||
// traffic lights, which self-corrects on the next resize/activate.
|
||||
// repaints). The window-bound effects — the Transparent↔Blurred
|
||||
// background flip and traffic-light re-pinning — are covered by
|
||||
// `Tty7App::reload_from_config`, which observes the `Config`
|
||||
// global with its window and re-runs `apply_theme(Some(window))`.
|
||||
crate::ui::theme::apply_theme(None, cx);
|
||||
// Schedule every window to redraw so the new palette shows at once.
|
||||
cx.refresh_windows();
|
||||
@@ -380,8 +382,7 @@ fn main() {
|
||||
WindowBounds::Fullscreen(bounds)
|
||||
}
|
||||
};
|
||||
let window_background =
|
||||
cx.update(|cx| crate::ui::theme::background_appearance(cx));
|
||||
let window_background = cx.update(|cx| crate::ui::theme::background_appearance(cx));
|
||||
let options = WindowOptions {
|
||||
window_bounds: Some(window_bounds),
|
||||
// Start from the component defaults but nudge the traffic lights
|
||||
|
||||
+33
-14
@@ -482,10 +482,13 @@ impl Tty7App {
|
||||
let mf_description = cx.new(|cx| InputState::new(window, cx).placeholder("description"));
|
||||
let sidebar_width = cx.global::<Config>().sidebar_width;
|
||||
// Live-apply hot-reloaded config: the watcher in `main.rs` swaps the
|
||||
// `Config` global on every `config.json` change, which fires this. Theme
|
||||
// and colors are handled separately by `apply_theme`; here we cover the
|
||||
// font knobs that live on `Tty7App`/the panes.
|
||||
let config_watch = cx.observe_global::<Config>(|this, cx| this.reload_from_config(cx));
|
||||
// `Config` global on every `config.json` change, which fires this. The
|
||||
// window-aware variant so the reload can re-run `apply_theme` with the
|
||||
// window (blur flip, traffic-light pinning) and re-sync the Appearance
|
||||
// opacity slider — the watcher task itself has no window handle.
|
||||
let config_watch = cx.observe_global_in::<Config>(window, |this, window, cx| {
|
||||
this.reload_from_config(window, cx)
|
||||
});
|
||||
// Repaint when any pane's git probe lands in the shared cache — the
|
||||
// sidebar's branch/diff lines read from it, and the probing pane's own
|
||||
// notify wouldn't re-render rows belonging to *other* panes. The open
|
||||
@@ -1014,7 +1017,12 @@ impl Tty7App {
|
||||
}
|
||||
|
||||
/// Set the global window-blur override from the Appearance switch.
|
||||
pub(crate) fn set_window_blur(&mut self, on: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub(crate) fn set_window_blur(
|
||||
&mut self,
|
||||
on: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
cx.global_mut::<Config>().window_blur = Some(on);
|
||||
apply_theme(Some(window), cx);
|
||||
cx.global::<Config>().save();
|
||||
@@ -1038,7 +1046,11 @@ impl Tty7App {
|
||||
/// Snap the Appearance opacity slider's thumb to the value now in effect.
|
||||
/// Needed whenever that value changes for a reason other than the user
|
||||
/// dragging it (theme switch, "Follow theme" reset).
|
||||
pub(crate) fn sync_window_opacity_slider(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub(crate) fn sync_window_opacity_slider(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let eff = Self::effective_window_opacity(cx);
|
||||
if let Some(slider) = self
|
||||
.active_settings()
|
||||
@@ -2969,15 +2981,13 @@ impl Tty7App {
|
||||
.step(0.01)
|
||||
.default_value(eff)
|
||||
});
|
||||
subs.push(cx.subscribe_in(
|
||||
&slider,
|
||||
window,
|
||||
|this, _s, ev: &SliderEvent, window, cx| {
|
||||
subs.push(
|
||||
cx.subscribe_in(&slider, window, |this, _s, ev: &SliderEvent, window, cx| {
|
||||
if let SliderEvent::Change(v) = ev {
|
||||
this.set_window_opacity(v.start(), window, cx);
|
||||
}
|
||||
},
|
||||
));
|
||||
}),
|
||||
);
|
||||
slider
|
||||
}
|
||||
|
||||
@@ -3135,7 +3145,14 @@ impl Tty7App {
|
||||
/// *our own* code mutated the global (every font setter and `set_preset`
|
||||
/// writes it), and — because we never write the global or `save()` from here
|
||||
/// — closes the save → watch → reload loop that would otherwise oscillate.
|
||||
fn reload_from_config(&mut self, cx: &mut Context<Self>) {
|
||||
fn reload_from_config(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
// Re-apply the theme with the window in hand: the watcher task calls
|
||||
// `apply_theme(None)` (colors/palette), but the window-bound effects — the
|
||||
// Transparent↔Blurred background flip and traffic-light re-pinning — only
|
||||
// happen here. Also keeps the Appearance opacity slider's thumb on a value
|
||||
// that was hand-edited in `config.json` or the theme file.
|
||||
apply_theme(Some(window), cx);
|
||||
self.sync_window_opacity_slider(window, cx);
|
||||
let config = cx.global::<Config>().clone();
|
||||
if config.cursor_style != self.terminal_cursor_style
|
||||
|| config.scrollback_limit != self.terminal_scrollback_limit
|
||||
@@ -3954,7 +3971,9 @@ impl Render for Tty7App {
|
||||
// Same gradient-aware paint as the root, so a gradient theme's
|
||||
// settings page doesn't snap to a flat color. A translucent
|
||||
// theme's alpha rides along, letting the background image show
|
||||
// through here too.
|
||||
// through here too. This second layer compounds the alpha over
|
||||
// the root's paint — deliberate: the overlay must occlude the
|
||||
// terminal behind it to stay readable.
|
||||
.bg(window_bg)
|
||||
.child(self.render_settings(cx))
|
||||
});
|
||||
|
||||
+11
-1
@@ -219,7 +219,17 @@ impl Tty7App {
|
||||
.inset_0()
|
||||
// Blocks mouse from reaching the terminal underneath.
|
||||
.occlude()
|
||||
.bg(cx.theme().background)
|
||||
// Same gradient/opacity-aware paint as the root and the settings
|
||||
// overlay, so a gradient or image theme doesn't snap to a flat
|
||||
// color here. On a translucent theme this second layer compounds
|
||||
// the alpha a little — deliberate: the overlay must occlude the
|
||||
// terminal behind it to stay readable.
|
||||
.bg(
|
||||
match cx.try_global::<crate::ui::presets::ActiveBackground>() {
|
||||
Some(bg) => crate::ui::theme::window_background(bg),
|
||||
None => cx.theme().background.into(),
|
||||
},
|
||||
)
|
||||
.text_color(cx.theme().foreground)
|
||||
.track_focus(&overlay.focus_handle)
|
||||
.on_key_down(cx.listener(|this, ev: &KeyDownEvent, window, cx| {
|
||||
|
||||
+8
-4
@@ -1263,9 +1263,9 @@ impl Tty7App {
|
||||
.into_any_element();
|
||||
let blur_switch = Switch::new("window-blur")
|
||||
.checked(blur)
|
||||
.on_click(cx.listener(|this, on: &bool, window, cx| {
|
||||
this.set_window_blur(*on, window, cx)
|
||||
}))
|
||||
.on_click(
|
||||
cx.listener(|this, on: &bool, window, cx| this.set_window_blur(*on, window, cx)),
|
||||
)
|
||||
.into_any_element();
|
||||
|
||||
v_flex()
|
||||
@@ -1341,7 +1341,11 @@ impl Tty7App {
|
||||
.w(px(240.))
|
||||
.child(
|
||||
Button::new("pick-theme-image")
|
||||
.label(if image.is_some() { "Change…" } else { "Choose…" })
|
||||
.label(if image.is_some() {
|
||||
"Change…"
|
||||
} else {
|
||||
"Choose…"
|
||||
})
|
||||
.small()
|
||||
.on_click(cx.listener(|this, _, _w, cx| this.pick_theme_image(cx))),
|
||||
)
|
||||
|
||||
+1
-4
@@ -117,10 +117,7 @@ pub(crate) fn apply_theme(mut window: Option<&mut Window>, cx: &mut App) {
|
||||
// Window opacity / blur: the global config override wins when set (so a
|
||||
// chosen translucency survives theme switches); otherwise the theme's own
|
||||
// values apply. Only an opacity below 1.0 makes the window translucent.
|
||||
let opacity = config
|
||||
.window_opacity
|
||||
.or(theme.opacity)
|
||||
.filter(|o| *o < 1.0);
|
||||
let opacity = config.window_opacity.or(theme.opacity).filter(|o| *o < 1.0);
|
||||
let blur = config.window_blur.unwrap_or(theme.blur);
|
||||
// Force the native macOS chrome (traffic lights, system menus, scrollbars)
|
||||
// into the theme's own light/dark mode regardless of the OS setting.
|
||||
|
||||
Reference in New Issue
Block a user