mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Title bar: on macOS the window buttons sit centred in it (#71)
AppKit draws the traffic lights 16 pt from the top of the window on macOS 27, but the title bar was 38 pt, so they sat above its centre line. On macOS the bar is now 32 pt, centred on them: content starts after the lights with the gap they keep from the top, and the Present button shrinks to 22 pt to keep a margin. Same approach as wordcraft #255 and gridcraft #323, with the light metrics measured on macOS 27. Linux, Windows and web are unchanged.
This commit is contained in:
@@ -12,6 +12,28 @@ use crate::widgets::{self, RIBBON_H, big_button, big_button_ex, color_split, dro
|
||||
|
||||
pub const TITLE_H: f32 = 38.0;
|
||||
|
||||
/// macOS draws the window into the title bar (`with_fullsize_content_view`) and AppKit paints the
|
||||
/// traffic lights itself, 16 pt from the top of the window on macOS 27. Moving them would take
|
||||
/// AppKit calls (`unsafe`, forbidden here), so on macOS the title bar is the band they are centred
|
||||
/// in, with the controls beside them (#71). Up to macOS 15 the lights are smaller and centred 14 pt
|
||||
/// from the top, about 2 pt above this bar's centre.
|
||||
pub const MAC_TITLE_BAR: f32 = 32.0;
|
||||
/// The traffic lights as AppKit lays them out, measured on macOS 27: button diameter,
|
||||
/// centre-to-centre pitch and the first button's left edge.
|
||||
const MAC_LIGHT: f32 = 14.0;
|
||||
const MAC_LIGHT_PITCH: f32 = 23.0;
|
||||
const MAC_LIGHTS_LEFT: f32 = 9.0;
|
||||
/// Where title-bar content starts on macOS: the end of the three lights plus the gap the lights
|
||||
/// keep from the top of the window.
|
||||
pub const MAC_CONTENT_LEFT: f32 = MAC_LIGHTS_LEFT + 2.0 * MAC_LIGHT_PITCH + MAC_LIGHT + (MAC_TITLE_BAR - MAC_LIGHT) / 2.0;
|
||||
|
||||
/// The title bar's height, the left edge of its content and the height of its tallest control.
|
||||
/// With the integrated title bar (macOS) the bar is shorter, so the controls shrink to keep a
|
||||
/// margin above and below them.
|
||||
fn title_geometry(integrated: bool) -> (f32, f32, f32) {
|
||||
if integrated { (MAC_TITLE_BAR, MAC_CONTENT_LEFT, 22.0) } else { (TITLE_H, 10.0, 26.0) }
|
||||
}
|
||||
|
||||
/// Is engine command `id` enabled right now?
|
||||
pub fn enabled(app: &SlideApp, id: &str) -> bool {
|
||||
match deckcraft_engine::find_command(id) {
|
||||
@@ -27,7 +49,8 @@ fn run(app: &mut SlideApp, id: &str, p: Value) {
|
||||
/// The window's title row: traffic-light space, quick access, centred title, actions.
|
||||
pub fn title_bar(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
egui::Panel::top("title_bar").exact_size(TITLE_H).frame(egui::Frame::NONE.fill(t.chrome)).show(ui, |ui| {
|
||||
let (height, left, control_h) = title_geometry(app.integrated_titlebar);
|
||||
egui::Panel::top("title_bar").exact_size(height).frame(egui::Frame::NONE.fill(t.chrome)).show(ui, |ui| {
|
||||
let rect = ui.max_rect();
|
||||
// Dragging the empty title bar moves the window.
|
||||
let drag = ui.interact(rect, ui.id().with("titledrag"), Sense::click_and_drag());
|
||||
@@ -38,7 +61,6 @@ pub fn title_bar(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let max = ui.input(|i| i.viewport().maximized.unwrap_or(false));
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Maximized(!max));
|
||||
}
|
||||
let left = if app.integrated_titlebar { 84.0 } else { 10.0 };
|
||||
let mut child = ui.new_child(
|
||||
egui::UiBuilder::new()
|
||||
.max_rect(Rect::from_min_max(pos2(rect.min.x + left, rect.min.y), pos2(rect.min.x + 420.0, rect.max.y)))
|
||||
@@ -78,8 +100,8 @@ pub fn title_bar(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let share = right.add(
|
||||
egui::Button::new(egui::RichText::new(" Present ").color(t.accent_text).font(theme::bold(13.0)))
|
||||
.fill(t.accent)
|
||||
.corner_radius(CornerRadius::same(13))
|
||||
.min_size(vec2(84.0, 26.0)),
|
||||
.corner_radius(control_h / 2.0)
|
||||
.min_size(vec2(84.0, control_h)),
|
||||
);
|
||||
if share.clicked() && has_doc {
|
||||
let from = app.session.active().map(|d| d.selection.slide).unwrap_or(0);
|
||||
@@ -2405,3 +2427,22 @@ fn paint_freeform_icon(p: &egui::Painter, r: Rect, name: &str, ink: Color32) {
|
||||
};
|
||||
p.add(egui::Shape::line(pts, egui::Stroke::new(1.3, ink)));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod title_bar_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mac_title_bar_centres_and_clears_the_traffic_lights() {
|
||||
let (h, left, control) = title_geometry(true);
|
||||
// AppKit centres the lights in its own band, so the bar is that band.
|
||||
assert_eq!(h, MAC_TITLE_BAR);
|
||||
// Content keeps the same gap after the lights as the lights keep from the top.
|
||||
let lights_end = MAC_LIGHTS_LEFT + 2.0 * MAC_LIGHT_PITCH + MAC_LIGHT;
|
||||
assert_eq!(left - lights_end, (h - MAC_LIGHT) / 2.0);
|
||||
// The tallest controls keep at least 4 pt above and below.
|
||||
assert!(control <= h - 8.0);
|
||||
// Other platforms keep their system frame and the original bar.
|
||||
assert_eq!(title_geometry(false), (TITLE_H, 10.0, 26.0));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user