mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
* claim: #8692 * Browser: remember the folded card across runs, optional reveal on hover (#8692) The browser card's fold (the chevron in its header) is now a preference, saved with the other layout state, so the next run starts the way the user left it. Preferences > Interface > Layout adds Shown / Reveal on hover: the browser then stays out of sight until the pointer comes over its place or the thin tab at its left edge, fading in like the navigation bar's Hide until hovered, and stays while the pointer is inside or a menu, drag or text edit started from it is open. It never folds in that mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Browser: move the reveal-on-hover messages to a pending locale file (#8692) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"en": {
|
||||
"prefs.browser_reveal_on_hover": "Reveal on hover",
|
||||
"prefs.browser_show_note": "Shown keeps the browser on screen. Reveal on hover keeps it out of sight until the pointer comes over its place or the thin tab at its left edge, and it stays open while the pointer is inside it or a menu from it is open.",
|
||||
"prefs.browser_shown": "Shown"
|
||||
},
|
||||
"ar": {
|
||||
"prefs.browser_reveal_on_hover": "إظهار عند التمرير",
|
||||
"prefs.browser_show_note": "«ظاهر» يُبقي المستعرض على الشاشة. «إظهار عند التمرير» يُخفيه حتى يمر المؤشر فوق مكانه أو فوق الشريط الرفيع عند حافته اليسرى، ويبقى مفتوحًا ما دام المؤشر داخله أو كانت قائمة منه مفتوحة.",
|
||||
"prefs.browser_shown": "ظاهر"
|
||||
},
|
||||
"de": {
|
||||
"prefs.browser_reveal_on_hover": "Beim Überfahren einblenden",
|
||||
"prefs.browser_show_note": "Angezeigt lässt den Browser auf dem Bildschirm. Beim Überfahren einblenden blendet ihn aus, bis der Zeiger über seinen Platz oder die schmale Lasche an seinem linken Rand kommt; er bleibt offen, solange der Zeiger darin ist oder ein Menü daraus offen ist.",
|
||||
"prefs.browser_shown": "Angezeigt"
|
||||
},
|
||||
"es": {
|
||||
"prefs.browser_reveal_on_hover": "Mostrar al pasar el puntero",
|
||||
"prefs.browser_show_note": "Visible mantiene el navegador en pantalla. Mostrar al pasar el puntero lo oculta hasta que el puntero llega a su lugar o a la pestaña fina de su borde izquierdo, y sigue abierto mientras el puntero está dentro o hay un menú suyo abierto.",
|
||||
"prefs.browser_shown": "Visible"
|
||||
},
|
||||
"fr": {
|
||||
"prefs.browser_reveal_on_hover": "Révéler au survol",
|
||||
"prefs.browser_show_note": "Affiché garde le navigateur à l'écran. Révéler au survol le cache jusqu'à ce que le pointeur passe sur sa place ou sur le fin onglet à son bord gauche, et il reste ouvert tant que le pointeur est dedans ou qu'un de ses menus est ouvert.",
|
||||
"prefs.browser_shown": "Affiché"
|
||||
},
|
||||
"he": {
|
||||
"prefs.browser_reveal_on_hover": "הצגה בריחוף",
|
||||
"prefs.browser_show_note": "״מוצג״ משאיר את הדפדפן על המסך. ״הצגה בריחוף״ מסתיר אותו עד שהמצביע מגיע למקומו או ללשונית הדקה בקצהו השמאלי, והוא נשאר פתוח כל עוד המצביע בתוכו או שתפריט ממנו פתוח.",
|
||||
"prefs.browser_shown": "מוצג"
|
||||
},
|
||||
"ja": {
|
||||
"prefs.browser_reveal_on_hover": "ホバーで表示",
|
||||
"prefs.browser_show_note": "「表示」はブラウザーを常に画面に表示します。「ホバーで表示」はポインターがその位置か左端の細いタブに来るまで隠し、ポインターが中にある間やそのメニューが開いている間は表示したままにします。",
|
||||
"prefs.browser_shown": "表示"
|
||||
},
|
||||
"vi": {
|
||||
"prefs.browser_reveal_on_hover": "Hiện khi di chuột",
|
||||
"prefs.browser_show_note": "Hiển thị giữ trình duyệt trên màn hình. Hiện khi di chuột ẩn nó cho đến khi con trỏ đến vị trí của nó hoặc thẻ mảnh ở cạnh trái, và nó vẫn mở khi con trỏ ở bên trong hoặc một menu của nó đang mở.",
|
||||
"prefs.browser_shown": "Hiển thị"
|
||||
},
|
||||
"zh": {
|
||||
"prefs.browser_reveal_on_hover": "悬停时显示",
|
||||
"prefs.browser_show_note": "“显示”让浏览器始终留在屏幕上。“悬停时显示”会将其隐藏,直到指针移到它的位置或其左边缘的细标签上;指针在其中或其菜单打开时,它保持打开。",
|
||||
"prefs.browser_shown": "显示"
|
||||
}
|
||||
}
|
||||
@@ -101,8 +101,6 @@ pub fn drawn_occurrences() -> Vec<(Vec<u64>, String, Rect, bool)> {
|
||||
/// Browser state kept between frames.
|
||||
#[derive(Default)]
|
||||
pub struct TreeState {
|
||||
/// The floating browser card is folded to its header.
|
||||
pub panel_collapsed: bool,
|
||||
/// Geometry and assembly queries, independent of the camera, selection and row visibility.
|
||||
model: Option<model::Model>,
|
||||
/// Folders closed by the user (keys like `c0/bodies`).
|
||||
@@ -809,6 +807,53 @@ pub(crate) fn collapsed_width() -> f32 {
|
||||
t.space_24 + t.space_10
|
||||
}
|
||||
|
||||
/// How the browser card shows (Preferences › Interface › Layout, #8692).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BrowserShow {
|
||||
/// Always on screen (folded to its header when the user folds it).
|
||||
#[default]
|
||||
Shown,
|
||||
/// Out of sight until the pointer comes over its place or the strip at its left edge, as the
|
||||
/// navigation bar's "Hide until hovered"; never folded, so the whole card is the target.
|
||||
Hover,
|
||||
}
|
||||
|
||||
/// Does the browser hide until hovered? Only its own card does: not the one sharing its column
|
||||
/// with History, nor the Render page's.
|
||||
pub(crate) fn reveals(app: &SolveApp) -> bool {
|
||||
app.preferences.browser_show == BrowserShow::Hover && !crate::timeline_left::stacked(app) && !crate::render_workspace::active(app)
|
||||
}
|
||||
|
||||
/// How much of the browser set to reveal on hover shows this frame (0 hidden, 1 shown). The
|
||||
/// pointer over last frame's card or the strip at its left edge shows it; a menu, popup, drag or
|
||||
/// text edit started while it showed keeps it shown. A drag started elsewhere (orbiting the view)
|
||||
/// passes over it without showing it.
|
||||
fn reveal_opacity(app: &SolveApp, ctx: &egui::Context, previous: Option<Rect>, edge: Rect) -> f32 {
|
||||
let card = egui::Id::new("sc_browser_card");
|
||||
let memory = card.with("reveal");
|
||||
let was = ctx.data(|d| d.get_temp::<bool>(memory)).unwrap_or(false);
|
||||
let over = ctx.input(|i| i.pointer.hover_pos()).is_some_and(|p| {
|
||||
let layer = ctx.layer_id_at(p);
|
||||
let on_card = previous.is_some_and(|r| r.contains(p)) && layer.is_some_and(|l| l.id == card);
|
||||
let on_edge = edge.contains(p) && layer.is_none_or(|l| l.order == egui::Order::Background);
|
||||
on_card || on_edge
|
||||
});
|
||||
let dragging = ctx.dragged_id().is_some();
|
||||
let busy = app.viewport.context_menu.is_some() || egui::Popup::is_any_open(ctx) || dragging || ctx.egui_wants_keyboard_input();
|
||||
let shown = (over && (was || !dragging)) || (was && busy);
|
||||
ctx.data_mut(|d| d.insert_temp(memory, shown));
|
||||
ctx.animate_value_with_time(memory.with("opacity"), f32::from(u8::from(shown)), crate::ui_kit::NAV_FADE_SECONDS)
|
||||
}
|
||||
|
||||
/// The thin tab at the hidden browser's left edge that says where it is.
|
||||
fn reveal_tab(ui: &egui::Ui, card: Rect, opacity: f32) {
|
||||
let t = Tokens::get();
|
||||
let x = card.left() + t.space_3;
|
||||
let tab = Rect::from_min_max(pos2(x, card.top() + t.space_8), pos2(x + t.space_3, card.top() + t.space_8 + t.space_24 * 2.0));
|
||||
ui.painter().rect_filled(tab, t.space_3 / 2.0, t.text_dim.gamma_multiply(opacity));
|
||||
}
|
||||
|
||||
/// One content-hugging browser; the shared container supplies floating or flush compact chrome.
|
||||
pub fn browser(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let available = ui.available_rect_before_wrap();
|
||||
@@ -839,12 +884,21 @@ pub fn browser(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
{
|
||||
available.min.x = (history.right() + gap).min(available.right()) - gap;
|
||||
}
|
||||
let collapsed = vertical && !stacked && app.preferences.timeline_left_browser_collapsed;
|
||||
let reveals = reveals(app);
|
||||
let collapsed = vertical && !stacked && !reveals && app.preferences.timeline_left_browser_collapsed;
|
||||
let bounds = if stacked {
|
||||
crate::timeline_left::stacked_rects(app, available).0
|
||||
} else {
|
||||
panel_bounds(available, if vertical { None } else { app.shell.history }, if collapsed { collapsed_width() } else { width }, gap)
|
||||
};
|
||||
let opacity = if reveals {
|
||||
let edge = Rect::from_min_max(pos2(available.left(), bounds.top()), pos2(bounds.left() + t.space_10, bounds.bottom()));
|
||||
crate::scenario::publish_target("browser:edge", edge);
|
||||
reveal_opacity(app, ui.ctx(), app.shell.browser, edge)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
app.shell.browser_hidden = reveals && opacity < 0.5;
|
||||
app.shell.browser = None;
|
||||
let min_height = HEADER_H + 2.0 * (CARD_INSET + 1.0);
|
||||
// A dialog in the left column holds the browser's place (`dialog_column`).
|
||||
@@ -855,19 +909,27 @@ pub fn browser(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let inner_height = (bounds.height() - frame.total_margin().sum().y).max(0.0);
|
||||
let card = Container::new(ui.ctx(), Surface::Panel).area(egui::Id::new("sc_browser_card")).fixed_pos(bounds.min).show(ui.ctx(), |ui| {
|
||||
let limits = Rect::from_min_max(available.min, pos2(available.right(), bounds.bottom()));
|
||||
let response = container.show_frame(ui, frame, limits, |ui| {
|
||||
ui.set_width(inner_width);
|
||||
ui.set_max_height(inner_height);
|
||||
if stacked {
|
||||
ui.set_min_height(inner_height);
|
||||
ui.set_clip_rect(ui.clip_rect().intersect(Rect::from_min_size(ui.max_rect().min, vec2(inner_width, inner_height))));
|
||||
}
|
||||
if collapsed {
|
||||
browser_collapsed_rail(app, ui);
|
||||
} else {
|
||||
browser_contents(app, ui, &mut acts, true);
|
||||
}
|
||||
});
|
||||
let response = ui
|
||||
.scope(|ui| {
|
||||
ui.set_opacity(opacity);
|
||||
container.show_frame(ui, frame, limits, |ui| {
|
||||
ui.set_width(inner_width);
|
||||
ui.set_max_height(inner_height);
|
||||
if stacked {
|
||||
ui.set_min_height(inner_height);
|
||||
ui.set_clip_rect(ui.clip_rect().intersect(Rect::from_min_size(ui.max_rect().min, vec2(inner_width, inner_height))));
|
||||
}
|
||||
if collapsed {
|
||||
browser_collapsed_rail(app, ui);
|
||||
} else {
|
||||
browser_contents(app, ui, &mut acts, true);
|
||||
}
|
||||
})
|
||||
})
|
||||
.inner;
|
||||
if reveals && opacity < 1.0 {
|
||||
reveal_tab(ui, response.response.rect, 1.0 - opacity);
|
||||
}
|
||||
if !collapsed {
|
||||
resize_column(app, ui, response.response.rect, width, "left-browser-rail");
|
||||
}
|
||||
@@ -1010,7 +1072,7 @@ fn browser_contents(app: &mut SolveApp, ui: &mut egui::Ui, acts: &mut Actions, c
|
||||
(app.preferences.timeline_left_browser_collapsed && app.shell.history_folding.is_none_or(|p| p[0] >= 1.0))
|
||||
|| ui.available_height() <= t.space_3
|
||||
} else {
|
||||
app.tree.panel_collapsed && !(app.ui.show_timeline && app.preferences.timeline_left)
|
||||
app.preferences.browser_collapsed && !reveals(app) && !(app.ui.show_timeline && app.preferences.timeline_left)
|
||||
})
|
||||
{
|
||||
return;
|
||||
@@ -1031,15 +1093,18 @@ fn browser_contents(app: &mut SolveApp, ui: &mut egui::Ui, acts: &mut Actions, c
|
||||
}
|
||||
|
||||
/// The header: the title, the body count and (on the floating card) a chevron that folds the
|
||||
/// card to just this row. The whole row is the button.
|
||||
/// card to just this row. The whole row is the button. A browser that reveals on hover never
|
||||
/// folds: the whole card is what the pointer finds.
|
||||
fn browser_header(app: &mut SolveApp, ui: &mut egui::Ui, collapsible: bool) {
|
||||
let collapsible = collapsible && !reveals(app);
|
||||
let t = Tokens::get();
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(ui.available_width(), HEADER_H), Sense::hover());
|
||||
let response = ui.interact(rect, ui.id().with("browser_header"), if collapsible { Sense::click() } else { Sense::hover() });
|
||||
crate::scenario::publish_target("browser:header", rect);
|
||||
let vertical = app.ui.show_timeline && app.preferences.timeline_left;
|
||||
let stacked = crate::timeline_left::stacked(app);
|
||||
let collapsed = collapsible && if stacked { app.preferences.timeline_left_browser_collapsed } else { !vertical && app.tree.panel_collapsed };
|
||||
let collapsed =
|
||||
collapsible && if stacked { app.preferences.timeline_left_browser_collapsed } else { !vertical && app.preferences.browser_collapsed };
|
||||
if vertical {
|
||||
crate::scenario::publish_handle("left-browser-toggle", rect.center());
|
||||
}
|
||||
@@ -1069,7 +1134,7 @@ fn browser_header(app: &mut SolveApp, ui: &mut egui::Ui, collapsible: bool) {
|
||||
} else if vertical {
|
||||
app.preferences.timeline_left_browser_collapsed = true;
|
||||
} else {
|
||||
app.tree.panel_collapsed = !app.tree.panel_collapsed;
|
||||
app.preferences.browser_collapsed = !app.preferences.browser_collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,21 +247,109 @@ fn browser_header_folds_the_floating_card_to_its_header_and_back() {
|
||||
h.call("ui.resize", json!({"width":1400,"height":900}));
|
||||
h.settle();
|
||||
let open = rect_of("browser:card").unwrap();
|
||||
assert!(!h.app.tree.panel_collapsed);
|
||||
assert!(!h.app.preferences.browser_collapsed);
|
||||
let at = handle_at("browser:header").unwrap();
|
||||
h.call("ui.click", json!({"x":at.x,"y":at.y}));
|
||||
h.settle();
|
||||
let folded = rect_of("browser:card").unwrap();
|
||||
assert!(h.app.tree.panel_collapsed);
|
||||
assert!(h.app.preferences.browser_collapsed);
|
||||
assert!(folded.height() < 60.0 && folded.height() < open.height(), "{folded:?} {open:?}");
|
||||
assert_eq!(folded.top(), open.top());
|
||||
assert!(drawn_rows().is_empty() || handle_at("row:Plate").is_none());
|
||||
h.call("ui.click", json!({"x":at.x,"y":at.y}));
|
||||
h.settle();
|
||||
assert!(!h.app.tree.panel_collapsed);
|
||||
assert!(!h.app.preferences.browser_collapsed);
|
||||
assert!((rect_of("browser:card").unwrap().height() - open.height()).abs() < 1.0);
|
||||
}
|
||||
|
||||
/// A folded browser stays folded in the next run, as the toolbar's pins do (#8692).
|
||||
#[test]
|
||||
fn the_folded_browser_is_remembered_across_runs() {
|
||||
let mut h = Harness::new();
|
||||
h.call("ui.resize", json!({"width":1400,"height":900}));
|
||||
h.settle();
|
||||
let open = rect_of("browser:card").unwrap();
|
||||
let at = handle_at("browser:header").unwrap();
|
||||
h.call("ui.click", json!({"x":at.x,"y":at.y}));
|
||||
h.settle();
|
||||
assert!(h.app.preferences.browser_collapsed);
|
||||
let saved = h.app.prefs();
|
||||
|
||||
let mut next = Harness::new();
|
||||
next.app.load_prefs(&saved);
|
||||
assert!(next.app.preferences.browser_collapsed, "the fold round-trips through the settings");
|
||||
next.call("ui.resize", json!({"width":1400,"height":900}));
|
||||
next.settle();
|
||||
let folded = rect_of("browser:card").unwrap();
|
||||
assert!(folded.height() < 60.0 && folded.height() < open.height(), "the next run starts folded: {folded:?}");
|
||||
let at = handle_at("browser:header").unwrap();
|
||||
next.call("ui.click", json!({"x":at.x,"y":at.y}));
|
||||
next.settle();
|
||||
let mut after = SolveApp::new(Default::default(), Default::default());
|
||||
after.load_prefs(&next.app.prefs());
|
||||
assert!(!after.preferences.browser_collapsed, "unfolding is remembered too");
|
||||
}
|
||||
|
||||
/// Reveal on hover (#8692): the browser is out of sight until the pointer comes over the strip at
|
||||
/// its left edge or its place, stays while the pointer is inside it or its menu is open, and
|
||||
/// never folds, even if it was folded before.
|
||||
#[test]
|
||||
fn a_browser_that_reveals_on_hover_shows_and_hides_with_the_pointer() {
|
||||
for mode in [LayoutMode::Floating, LayoutMode::Docked] {
|
||||
let mut h = Harness::new();
|
||||
h.app.ui.layout = mode;
|
||||
h.app.run("solid.box", json!({"length":10,"width":10,"height":10,"operation":"new_body","body_name":"One"})).unwrap();
|
||||
h.call("ui.resize", json!({"width":1400,"height":900}));
|
||||
h.settle();
|
||||
let shown = rect_of("browser:card").unwrap();
|
||||
h.app.preferences.browser_collapsed = true;
|
||||
h.app.preferences.browser_show = BrowserShow::Hover;
|
||||
let away = json!({"x":900,"y":450});
|
||||
h.call("ui.move", away.clone());
|
||||
h.settle();
|
||||
assert!(h.app.shell.browser_hidden, "{mode:?}: out of sight while the pointer is elsewhere");
|
||||
let card = rect_of("browser:card").unwrap();
|
||||
assert!((card.height() - shown.height()).abs() < 1.0, "{mode:?}: the card keeps its unfolded place: {card:?} {shown:?}");
|
||||
|
||||
let edge = handle_at("browser:edge").unwrap();
|
||||
h.call("ui.move", json!({"x":edge.x,"y":edge.y}));
|
||||
h.settle();
|
||||
assert!(!h.app.shell.browser_hidden, "{mode:?}: the left edge reveals it");
|
||||
let inside = rect_of("browser:card").unwrap().center();
|
||||
h.call("ui.move", json!({"x":inside.x,"y":inside.y}));
|
||||
h.settle();
|
||||
assert!(!h.app.shell.browser_hidden, "{mode:?}: it stays while the pointer is inside");
|
||||
h.call("ui.move", away.clone());
|
||||
h.settle();
|
||||
assert!(h.app.shell.browser_hidden, "{mode:?}: leaving hides it again");
|
||||
|
||||
h.call("ui.move", json!({"x":inside.x,"y":inside.y}));
|
||||
h.settle();
|
||||
let header = handle_at("browser:header").unwrap();
|
||||
h.call("ui.click", json!({"x":header.x,"y":header.y}));
|
||||
h.settle();
|
||||
assert!(rect_of("browser:card").unwrap().height() > 60.0, "{mode:?}: the header does not fold it");
|
||||
h.step(&json!({"click":{"handle":"row:One"},"button":"right"})).unwrap();
|
||||
assert!(h.app.viewport.context_menu.is_some(), "{mode:?}: the row's menu opens");
|
||||
h.call("ui.move", away.clone());
|
||||
h.settle();
|
||||
assert!(!h.app.shell.browser_hidden, "{mode:?}: its menu keeps it shown");
|
||||
h.step(&json!({"key":"Escape"})).unwrap();
|
||||
h.call("ui.move", away.clone());
|
||||
h.settle();
|
||||
assert!(h.app.shell.browser_hidden, "{mode:?}: closing the menu away from it hides it");
|
||||
|
||||
h.app.preferences.browser_show = BrowserShow::Shown;
|
||||
h.settle();
|
||||
assert!(!h.app.shell.browser_hidden);
|
||||
assert!(rect_of("browser:card").unwrap().height() < 60.0, "{mode:?}: Shown folds it again as it was left");
|
||||
let mut restored = SolveApp::new(Default::default(), Default::default());
|
||||
h.app.preferences.browser_show = BrowserShow::Hover;
|
||||
restored.load_prefs(&h.app.prefs());
|
||||
assert_eq!(restored.preferences.browser_show, BrowserShow::Hover);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tiny or odd window sizes never panic: the cards leave out what cannot fit.
|
||||
#[test]
|
||||
fn tiny_floating_windows_never_panic() {
|
||||
|
||||
@@ -65,7 +65,7 @@ pub(crate) fn rect(app: &SolveApp, available: Rect, width: f32) -> Rect {
|
||||
let translucent = panel.floating() && app.preferences.browser_float;
|
||||
if app.ui.show_browser && !stacked && !translucent {
|
||||
let from = if history { right + gap } else { left };
|
||||
let collapsed = history && app.preferences.timeline_left_browser_collapsed;
|
||||
let collapsed = history && app.preferences.timeline_left_browser_collapsed && !crate::browser::reveals(app);
|
||||
right = from + if collapsed { crate::browser::collapsed_width() } else { browser_width };
|
||||
}
|
||||
let bottom = app.shell.history.filter(|_| app.ui.show_timeline && !app.preferences.timeline_left).map_or(available.bottom(), |h| h.top()) - gap;
|
||||
|
||||
@@ -1073,6 +1073,12 @@ preference_settings! {
|
||||
/// Draw the parts browser on a translucent card over the 3D view instead of a column of its
|
||||
/// own, so the workspace keeps the whole window width.
|
||||
browser_float: bool = false, PAGE_INTERFACE;
|
||||
/// The browser card folded to its header with the chevron in that header, kept from one run
|
||||
/// to the next like the toolbar's pins (#8692).
|
||||
browser_collapsed: bool = false, PAGE_INTERFACE;
|
||||
/// Whether the browser card is always shown or kept out of sight until the pointer comes to
|
||||
/// it, as the navigation bar's "Hide until hovered" does (#8692).
|
||||
browser_show: crate::browser::BrowserShow = Default::default(), PAGE_INTERFACE;
|
||||
/// Where command dialogs open: at the view's edge, or in the left column in place of the
|
||||
/// browser and the History there (#6707).
|
||||
dialog_place: crate::dialog_column::DialogPlace = Default::default(), PAGE_INTERFACE;
|
||||
@@ -4761,6 +4767,8 @@ mod tests {
|
||||
("history_sketches", json!("under_feature"), PAGE_INTERFACE),
|
||||
("history_play_button", json!(false), PAGE_INTERFACE),
|
||||
("browser_float", json!(true), PAGE_INTERFACE),
|
||||
("browser_collapsed", json!(true), PAGE_INTERFACE),
|
||||
("browser_show", json!("hover"), PAGE_INTERFACE),
|
||||
("dialog_place", json!("left_column"), PAGE_INTERFACE),
|
||||
("color_vision", json!("protan"), PAGE_INTERFACE),
|
||||
("viewport_theme", json!("light"), PAGE_INTERFACE),
|
||||
|
||||
@@ -208,6 +208,20 @@ pub(super) fn interface(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
}
|
||||
});
|
||||
});
|
||||
// Out of sight until the pointer comes to it (#8692); the browser sharing its
|
||||
// column with History keeps its place.
|
||||
ui_kit::row(ui, "", |ui| {
|
||||
use crate::browser::BrowserShow;
|
||||
ui.add_enabled_ui(!crate::timeline_left::stacked(app), |ui| {
|
||||
let mut selected = usize::from(app.preferences.browser_show == BrowserShow::Hover);
|
||||
let options = [app.tr("prefs.browser_shown"), app.tr("prefs.browser_reveal_on_hover")];
|
||||
let segments = ui.scope(|ui| ui_kit::segmented(ui, &mut selected, &options, Default::default()));
|
||||
segments.response.on_hover_text(app.tr("prefs.browser_show_note"));
|
||||
if segments.inner {
|
||||
app.preferences.browser_show = if selected == 1 { BrowserShow::Hover } else { BrowserShow::Shown };
|
||||
}
|
||||
});
|
||||
});
|
||||
// Where command dialogs open (#6707).
|
||||
ui_kit::row(ui, app.tr("prefs.dialogs"), |ui| {
|
||||
use crate::dialog_column::DialogPlace;
|
||||
|
||||
@@ -321,7 +321,7 @@ pub(super) fn open_category(app: &mut SolveApp, kind: &str) {
|
||||
};
|
||||
app.ui.show_browser = true;
|
||||
let _ = app.run("view.render_browser", json!({"open":true}));
|
||||
app.tree.panel_collapsed = false;
|
||||
app.preferences.browser_collapsed = false;
|
||||
}
|
||||
pub(super) fn choose(app: &mut SolveApp, view: &mut View, kind: &str, id: &str) -> Result<(), String> {
|
||||
switch_kind(view, kind);
|
||||
|
||||
@@ -3794,7 +3794,7 @@ fn render_workspace_focus_restores_docks_without_design_history_or_camera_change
|
||||
h.app.preferences.render_browser_width = 350.0;
|
||||
h.app.preferences.render_inspector_width = 390.0;
|
||||
h.app.preferences.render_shots_strip = shots;
|
||||
h.app.tree.panel_collapsed = folded;
|
||||
h.app.preferences.browser_collapsed = folded;
|
||||
h.app.shell.render_browser_open = folded;
|
||||
h.frames(3);
|
||||
let dock_preferences = h.app.prefs();
|
||||
@@ -3829,7 +3829,7 @@ fn render_workspace_focus_restores_docks_without_design_history_or_camera_change
|
||||
assert_eq!(restored_edge.as_ref().map(std::mem::discriminant), edge.as_ref().map(std::mem::discriminant));
|
||||
}
|
||||
assert_eq!(h.app.prefs(), dock_preferences);
|
||||
assert_eq!(h.app.tree.panel_collapsed, folded);
|
||||
assert_eq!(h.app.preferences.browser_collapsed, folded);
|
||||
assert_eq!(h.app.shell.render_browser_open, folded);
|
||||
assert_eq!((h.app.cam, h.app.session.camera), camera);
|
||||
assert!(Arc::ptr_eq(&h.app.session.doc, &document));
|
||||
|
||||
@@ -99,6 +99,9 @@ pub(crate) struct State {
|
||||
/// Actual floating workspace surfaces and their optional canvas reservation.
|
||||
pub workspace_panels: std::collections::BTreeMap<&'static str, (Rect, Option<crate::ui_kit::Edge>)>,
|
||||
pub browser: Option<Rect>,
|
||||
/// The browser set to reveal on hover is out of sight this frame (#8692); its place stays
|
||||
/// in `browser`, which the pointer reveals.
|
||||
pub browser_hidden: bool,
|
||||
pub history: Option<Rect>,
|
||||
/// In-memory seam drag width, never serialized as a preference.
|
||||
pub history_drag_width: Option<f32>,
|
||||
|
||||
@@ -32,7 +32,7 @@ fn appearance_and_browser_folds_keep_handles_and_render_both_states() {
|
||||
h.step(&json!({"click":{"handle":"browser:header"}})).unwrap();
|
||||
}
|
||||
assert_eq!(h.app.dialog.as_ref().unwrap().collapsed, !open);
|
||||
assert_eq!(h.app.tree.panel_collapsed, !open);
|
||||
assert_eq!(h.app.preferences.browser_collapsed, !open);
|
||||
assert_eq!(scenario::handle_at("fold:Bodies").is_some(), open);
|
||||
assert_eq!(scenario::handle_at("appearance:search").is_some(), open);
|
||||
// Fresh context transfers all textures to the software renderer, including fonts.
|
||||
|
||||
Reference in New Issue
Block a user