Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions crates/app/src/ai/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ fn run(
// The bridge command and its token, as codex config: the value side of
// each `-c` is TOML.
builder = builder
// A canvas conversation is deliberately self-contained. The
// user's Codex memories belong to their coding work, and feeding
// them into this embedded assistant both confuses its role and
// can disclose context from an unrelated project. Do not consume
// those memories, and do not turn photo-editing conversations into
// inputs for future global memories either.
.config_override("features.memories", "false")
.config_override("memories.use_memories", "false")
.config_override("memories.generate_memories", "false")
.config_override(
"mcp_servers.schist.command",
toml_string(&exe.display().to_string()),
Expand Down
4 changes: 1 addition & 3 deletions crates/app/src/dialogs/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ pub(super) fn update_available(
.child("Installing the update\u{2026}".to_string())
.child(progress_bar(1.0)),
(Some(installer), None) => body.child(format!(
"Schist can download it ({}) and install it over this copy. It \
restarts once the update is in place, and asks about any \
unsaved documents on the way.",
"Schist can download it ({}) and install it over this copy.",
megabytes(installer.size)
)),
(None, None) => body.child(
Expand Down
12 changes: 10 additions & 2 deletions crates/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ mod workspace;

use actions::{HideApp, HideOthers, Quit, ShowAll};
use gpui::{
px, size, App, AppContext as _, Application, AsyncApp, Bounds, WindowBounds, WindowHandle,
WindowOptions,
px, size, App, AppContext as _, Application, AsyncApp, Bounds, TitlebarOptions, WindowBounds,
WindowHandle, WindowOptions,
};
use schist_plugin_api::{CodecPlugin, PluginManifest, PluginRegistry};
use std::cell::RefCell;
Expand Down Expand Up @@ -343,6 +343,14 @@ fn main() {
.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
titlebar: Some(TitlebarOptions {
title: Some("Schist".into()),
// AppKit keeps the traffic lights while allowing
// the workspace to paint a title bar matching its
// own light or dark theme.
appears_transparent: cfg!(target_os = "macos"),
..Default::default()
}),
..Default::default()
},
|_window, cx| {
Expand Down
2 changes: 2 additions & 0 deletions crates/app/src/panels/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ mod rulers;
mod sliders;
mod status;
mod tabs;
mod titlebar;
mod toolbar;

#[cfg(not(target_arch = "wasm32"))]
Expand All @@ -59,6 +60,7 @@ pub use rulers::*;
pub use sliders::*;
pub use status::*;
pub use tabs::*;
pub use titlebar::*;
pub use toolbar::*;

fn swatch_hex(c: Rgba) -> gpui::Rgba {
Expand Down
5 changes: 4 additions & 1 deletion crates/app/src/panels/sliders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,11 @@ pub(super) fn slider(
}
row.child(track).child(
div()
.w(px(34.0))
// "180 px" is wider than the old 34px slot. Keep quantities
// on one line, including at the largest three-digit values.
.w(px(44.0))
.flex_none()
.whitespace_nowrap()
.text_size(px(11.0))
.child(display),
)
Expand Down
35 changes: 35 additions & 0 deletions crates/app/src/panels/titlebar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! Window title chrome painted into AppKit's transparent title-bar area.

use super::*;

/// A title bar that follows Schist's own theme. macOS keeps drawing and
/// operating the traffic-light controls above this row; the centred label
/// stays clear of them and follows the active document.
pub fn title_bar(ws: &Workspace) -> impl IntoElement {
let title: SharedString = match ws.doc.as_ref() {
Some(doc) if doc.dirty => format!("{} • — Schist", doc.title).into(),
Some(doc) => format!("{} — Schist", doc.title).into(),
None => "Schist".into(),
};

div()
.flex()
.flex_none()
.items_center()
.justify_center()
.h(px(28.0))
.w_full()
.bg(gpui::rgb(palette().panel_bg))
.border_b_1()
.border_color(gpui::rgb(palette().panel_edge))
.child(
div()
.max_w(px(520.0))
.overflow_hidden()
.whitespace_nowrap()
.text_ellipsis()
.text_size(px(11.0))
.text_color(gpui::rgb(palette().text_dim))
.child(title),
)
}
61 changes: 48 additions & 13 deletions crates/app/src/workspace/library_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2298,6 +2298,24 @@ fn search_models_downloading(ws: &Workspace) -> bool {
.any(|d| SEARCH_MODELS.contains(&d.id))
}

fn search_model_link(
id: &'static str,
label: &'static str,
url: &'static str,
cx: &mut Context<Workspace>,
) -> impl IntoElement {
div()
.id(id)
.cursor_pointer()
.text_color(gpui::rgb(crate::ui::palette().accent))
.hover(|style| style.text_color(gpui::rgb(crate::ui::palette().accent_hover)))
.on_mouse_down(
MouseButton::Left,
cx.listener(move |_ws, _event, _window, cx| cx.open_url(url)),
)
.child(label)
}

/// The licences behind photo search, and the button that accepts them.
/// One dialog for both models: they are downloaded as a pair.
pub(crate) fn search_models_dialog(cx: &mut Context<Workspace>) -> impl IntoElement {
Expand Down Expand Up @@ -2325,21 +2343,38 @@ pub(crate) fn search_models_dialog(cx: &mut Context<Workspace>) -> impl IntoElem
"{} \u{b7} {:.0} MB",
spec.name,
spec.bytes as f64 / (1 << 20) as f64
))))
.child(
div()
.text_size(px(11.0))
.text_color(gpui::rgb(crate::ui::palette().text_dim))
.child(SharedString::from(spec.license)),
)
.child(
div()
.text_size(px(11.0))
.text_color(gpui::rgb(crate::ui::palette().text_dim))
.child(SharedString::from(spec.note)),
),
)))),
);
}
body = body.child(
div()
.flex()
.flex_row()
.items_center()
.gap_1()
.text_size(px(11.0))
.text_color(gpui::rgb(crate::ui::palette().text_dim))
.child(search_model_link(
"mobileclip-source",
"MobileCLIP by Apple",
"https://github.com/apple/ml-mobileclip",
cx,
))
.child("\u{b7}")
.child(search_model_link(
"mobileclip-export",
"ONNX export by Xenova",
"https://huggingface.co/Xenova/mobileclip_s0",
cx,
))
.child("\u{b7}")
.child(search_model_link(
"mobileclip-license",
"License",
"https://github.com/apple/ml-mobileclip/blob/main/LICENSE",
cx,
)),
);
body = body.child(
div()
.pt_1()
Expand Down
3 changes: 3 additions & 0 deletions crates/app/src/workspace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,9 @@ pub struct PaintJob {
highlights: Vec<Bounds<Pixels>>,
outlines: Vec<(Bounds<Pixels>, gpui::Hsla)>,
polylines: Vec<(Vec<Point<Pixels>>, gpui::Hsla)>,
/// Text carets, painted as a dark stroke under a light one so either
/// half contrasts with the artwork below it.
carets: Vec<Vec<Point<Pixels>>>,
/// Marching-ants dashes.
ants: Ants,
circles: Vec<Bounds<Pixels>>,
Expand Down
25 changes: 25 additions & 0 deletions crates/app/src/workspace/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ impl Workspace {
gpui::rgb(0xFFFFFF).into(),
));
}
Overlay::Caret { x1, y1, x2, y2 } => {
job.carets.push(vec![to_screen(x1, y1), to_screen(x2, y2)]);
}
Overlay::Circle { cx: ccx, cy, r } => {
let d = r * 2.0 * zoom;
job.circles.push(Bounds {
Expand Down Expand Up @@ -493,6 +496,24 @@ impl Workspace {
window.paint_path(path, color);
}
}
// A white hairline disappears on white text or a
// pale photograph. The dark three-pixel underlay
// leaves a one-pixel rim around the white centre,
// giving the insertion caret contrast everywhere.
for pts in job.carets {
for (width, color) in
[(3.0, gpui::rgb(0x000000)), (1.0, gpui::rgb(0xFFFFFF))]
{
let mut pb = PathBuilder::stroke(px(width));
pb.move_to(pts[0]);
for p in &pts[1..] {
pb.line_to(*p);
}
if let Ok(path) = pb.build() {
window.paint_path(path, color);
}
}
}
for bounds in job.circles {
let r = bounds.size.width / 2.0;
window.paint_quad(gpui::quad(
Expand Down Expand Up @@ -806,6 +827,10 @@ impl Render for Workspace {
}))
.on_action(cx.listener(|ws, _: &NextTab, _w, cx| ws.cycle_tab(1, cx)))
.on_action(cx.listener(|ws, _: &PrevTab, _w, cx| ws.cycle_tab(-1, cx)))
// With AppKit's title bar transparent, this row occupies the
// native drag/traffic-light area and follows the app theme.
// Other platforms retain their native window decorations.
.children((chrome && cfg!(target_os = "macos")).then(|| panels::title_bar(self)))
.children(in_window_menus.then(|| panels::menu_bar(self, cx)))
.children(editor_chrome.then(|| panels::tool_options_bar(self, cx)))
.children(editor_chrome.then(|| panels::tab_bar(self, cx)))
Expand Down
3 changes: 3 additions & 0 deletions crates/plugin-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ pub enum Overlay {
Circle { cx: f32, cy: f32, r: f32 },
/// Straight line segment.
Line { x1: f32, y1: f32, x2: f32, y2: f32 },
/// Text insertion caret. The host gives this a contrasting outline so
/// it stays visible over both light and dark artwork.
Caret { x1: f32, y1: f32, x2: f32, y2: f32 },
/// A note's pin, filled in the note's own colour. Drawn at a fixed
/// *screen* size like Photoshop's, so a note stays findable and
/// clickable whether the document is at 5% or 1600%.
Expand Down
2 changes: 1 addition & 1 deletion plugins/tools-type/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,7 @@ impl ToolPlugin for TypeTool {
if let Some(caret) = schist_text_engine::caret_at(spec, session.caret) {
let x = ox + caret.x;
let y = oy + caret.top;
out.push(Overlay::Line {
out.push(Overlay::Caret {
x1: x,
y1: y,
x2: x,
Expand Down
Loading