theme palette, command bridge, missing shortcuts

This commit is contained in:
jess 2026-04-08 03:30:34 -07:00
parent 36895cd548
commit 6f36f9c3df
8 changed files with 244 additions and 65 deletions

View File

@ -6,11 +6,6 @@ import UniformTypeIdentifiers
extension Notification.Name {
static let focusEditor = Notification.Name("focusEditor")
static let focusTitle = Notification.Name("focusTitle")
static let formatDocument = Notification.Name("formatDocument")
static let insertTable = Notification.Name("insertTable")
static let boldSelection = Notification.Name("boldSelection")
static let italicizeSelection = Notification.Name("italicizeSelection")
static let smartEval = Notification.Name("smartEval")
}
class WindowController {
@ -63,6 +58,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
observeDocumentTitle()
observeDocumentText()
syncThemeToViewport()
DocumentBrowserController.shared = DocumentBrowserController(appState: appState)
@ -281,19 +277,19 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
@objc private func boldSelection() {
NotificationCenter.default.post(name: .boldSelection, object: nil)
viewport?.sendCommand(1)
}
@objc private func italicizeSelection() {
NotificationCenter.default.post(name: .italicizeSelection, object: nil)
viewport?.sendCommand(2)
}
@objc private func insertTable() {
NotificationCenter.default.post(name: .insertTable, object: nil)
viewport?.sendCommand(3)
}
@objc private func smartEval() {
NotificationCenter.default.post(name: .smartEval, object: nil)
viewport?.sendCommand(4)
}
@objc private func openNote() {
@ -412,7 +408,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}()
@objc private func formatDocument() {
NotificationCenter.default.post(name: .formatDocument, object: nil)
viewport?.sendCommand(10)
}
@objc private func openSettings() {
@ -421,9 +417,24 @@ class AppDelegate: NSObject, NSApplicationDelegate {
@objc private func settingsDidChange() {
window.backgroundColor = Theme.current.base
syncThemeToViewport()
window.contentView?.needsDisplay = true
}
private func syncThemeToViewport() {
let mode = ConfigManager.shared.themeMode
let name: String
switch mode {
case "dark": name = "mocha"
case "light": name = "latte"
default:
let appearance = NSApp.effectiveAppearance
let isDark = appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
name = isDark ? "mocha" : "latte"
}
viewport?.setTheme(name)
}
@objc private func toggleBrowser() {
DocumentBrowserController.shared?.toggle()
}

View File

@ -159,7 +159,7 @@ class IcedViewportView: NSView {
if cmd && !shift {
switch chars {
case "a", "b", "c", "i", "v", "x", "z", "p", "t",
case "a", "b", "c", "e", "f", "g", "i", "v", "x", "z", "p", "t",
"=", "+", "-", "0":
keyDown(with: event)
return true
@ -214,4 +214,16 @@ class IcedViewportView: NSView {
viewport_free_string(cstr)
return result
}
func sendCommand(_ command: UInt32) {
guard let h = viewportHandle else { return }
viewport_send_command(h, command)
}
func setTheme(_ name: String) {
guard let h = viewportHandle else { return }
name.withCString { cstr in
viewport_set_theme(h, cstr)
}
}
}

View File

@ -53,4 +53,8 @@ char *viewport_get_text(struct ViewportHandle *handle);
void viewport_free_string(char *s);
void viewport_set_theme(struct ViewportHandle *_handle, const char *name);
void viewport_send_command(struct ViewportHandle *handle, uint32_t command);
#endif /* ACORD_VIEWPORT_H */

View File

@ -13,6 +13,7 @@ use iced_widget::container;
use iced_widget::markdown;
use iced_widget::text_editor::{self, Action, Binding, Cursor, KeyPress, Motion, Position, Status, Style};
use iced_wgpu::core::text::highlighter::Format;
use crate::palette;
use crate::syntax::{self, SyntaxHighlighter, SyntaxSettings};
#[derive(Debug, Clone)]
@ -48,17 +49,18 @@ pub struct EditorState {
}
fn md_style() -> markdown::Style {
let p = palette::current();
markdown::Style {
font: Font::default(),
inline_code_highlight: Highlight {
background: Color::from_rgb(0.188, 0.188, 0.259).into(),
background: p.surface0.into(),
border: border::rounded(4),
},
inline_code_padding: padding::left(2).right(2),
inline_code_color: Color::from_rgb(0.651, 0.890, 0.631),
inline_code_color: p.green,
inline_code_font: Font::MONOSPACE,
code_block_font: Font::MONOSPACE,
link_color: Color::from_rgb(0.537, 0.706, 0.980),
link_color: p.blue,
}
}
@ -402,12 +404,15 @@ impl EditorState {
)
.width(Length::Fill)
.height(Length::Fill)
.style(|_theme: &Theme| container::Style {
background: Some(Background::Color(Color::from_rgb(0.08, 0.08, 0.10))),
border: Border::default(),
text_color: Some(Color::from_rgb(0.804, 0.839, 0.957)),
shadow: Shadow::default(),
snap: false,
.style(|_theme: &Theme| {
let p = palette::current();
container::Style {
background: Some(Background::Color(p.base)),
border: Border::default(),
text_color: Some(p.text),
shadow: Shadow::default(),
snap: false,
}
})
.into()
} else {
@ -420,12 +425,15 @@ impl EditorState {
.padding(Padding { top: top_pad, right: 8.0, bottom: 8.0, left: 8.0 })
.wrapping(Wrapping::Word)
.key_binding(macos_key_binding)
.style(|_theme, _status| Style {
background: Background::Color(Color::from_rgb(0.08, 0.08, 0.10)),
border: Border::default(),
placeholder: Color::from_rgb(0.4, 0.4, 0.4),
value: Color::WHITE,
selection: Color::from_rgba(0.3, 0.5, 0.8, 0.4),
.style(|_theme, _status| {
let p = palette::current();
Style {
background: Background::Color(p.base),
border: Border::default(),
placeholder: p.overlay0,
value: p.text,
selection: Color { a: 0.4, ..p.blue },
}
});
let settings = SyntaxSettings {
@ -478,18 +486,21 @@ impl EditorState {
iced_widget::text(format!("{mode_label} Ln {line}, Col {col}"))
.font(Font::MONOSPACE)
.size(11.0)
.color(Color::from_rgb(0.55, 0.55, 0.55))
.color(palette::current().overlay1)
.into(),
])
)
.width(Length::Fill)
.padding(Padding { top: 3.0, right: 10.0, bottom: 3.0, left: 10.0 })
.style(|_theme: &Theme| container::Style {
background: Some(Background::Color(Color::from_rgb(0.12, 0.12, 0.14))),
border: Border::default(),
text_color: None,
shadow: Shadow::default(),
snap: false,
.style(|_theme: &Theme| {
let p = palette::current();
container::Style {
background: Some(Background::Color(p.mantle)),
border: Border::default(),
text_color: None,
shadow: Shadow::default(),
snap: false,
}
});
let mut col_items: Vec<Element<'_, Message, Theme, iced_wgpu::Renderer>> =
@ -539,7 +550,7 @@ impl canvas::Program<Message, Theme, iced_wgpu::Renderer> for Gutter {
frame.fill_rectangle(
Point::ORIGIN,
bounds.size(),
Color::from_rgb(0.06, 0.06, 0.08),
palette::current().crust,
);
let first_visible = (self.scroll_offset / lh).floor() as usize;
@ -572,10 +583,11 @@ impl canvas::Program<Message, Theme, iced_wgpu::Renderer> for Gutter {
continue;
}
source_num += 1;
let p = palette::current();
let color = if line_idx == self.cursor_line {
Color::from_rgb(0.55, 0.55, 0.62)
p.overlay1
} else {
Color::from_rgb(0.35, 0.35, 0.42)
p.surface2
};
frame.fill_text(canvas::Text {
content: format!("{}", source_num),

View File

@ -12,6 +12,7 @@ use raw_window_handle::{
};
use crate::editor::{EditorState, Message};
use crate::palette;
use crate::ViewportHandle;
struct MacClipboard;
@ -209,10 +210,9 @@ pub fn render(handle: &mut ViewportHandle) {
ui.draw(&mut handle.renderer, &theme, &style, handle.cursor);
handle.cache = ui.into_cache();
let bg = Color::from_rgb(0.08, 0.08, 0.10);
handle
.renderer
.present(Some(bg), handle.format, &view, &handle.viewport);
.present(Some(palette::current().base), handle.format, &view, &handle.viewport);
frame.present();
}

View File

@ -3,6 +3,7 @@ use std::ffi::{c_char, c_void, CStr, CString};
mod bridge;
mod editor;
mod handle;
pub mod palette;
mod syntax;
pub use acord_core::*;
@ -172,3 +173,34 @@ pub extern "C" fn viewport_free_string(s: *mut c_char) {
if s.is_null() { return; }
unsafe { drop(CString::from_raw(s)); }
}
#[no_mangle]
pub extern "C" fn viewport_set_theme(_handle: *mut ViewportHandle, name: *const c_char) {
let s = if name.is_null() {
"mocha"
} else {
unsafe { CStr::from_ptr(name) }.to_str().unwrap_or("mocha")
};
palette::set_theme(s);
}
#[no_mangle]
pub extern "C" fn viewport_send_command(handle: *mut ViewportHandle, command: u32) {
let h = match unsafe { handle.as_mut() } {
Some(h) => h,
None => return,
};
let msg = match command {
1 => editor::Message::ToggleBold,
2 => editor::Message::ToggleItalic,
3 => editor::Message::InsertTable,
4 => editor::Message::SmartEval,
5 => editor::Message::Evaluate,
6 => editor::Message::TogglePreview,
7 => editor::Message::ZoomIn,
8 => editor::Message::ZoomOut,
9 => editor::Message::ZoomReset,
_ => return,
};
h.state.update(msg);
}

106
viewport/src/palette.rs Normal file
View File

@ -0,0 +1,106 @@
use iced_wgpu::core::Color;
use std::cell::RefCell;
#[derive(Clone, Copy)]
pub struct Palette {
pub rosewater: Color,
pub flamingo: Color,
pub pink: Color,
pub mauve: Color,
pub red: Color,
pub maroon: Color,
pub peach: Color,
pub yellow: Color,
pub green: Color,
pub teal: Color,
pub sky: Color,
pub sapphire: Color,
pub blue: Color,
pub lavender: Color,
pub text: Color,
pub subtext1: Color,
pub subtext0: Color,
pub overlay2: Color,
pub overlay1: Color,
pub overlay0: Color,
pub surface2: Color,
pub surface1: Color,
pub surface0: Color,
pub base: Color,
pub mantle: Color,
pub crust: Color,
}
pub static MOCHA: Palette = Palette {
rosewater: Color::from_rgb(0.961, 0.878, 0.863),
flamingo: Color::from_rgb(0.949, 0.804, 0.804),
pink: Color::from_rgb(0.961, 0.761, 0.906),
mauve: Color::from_rgb(0.796, 0.651, 0.969),
red: Color::from_rgb(0.953, 0.545, 0.659),
maroon: Color::from_rgb(0.922, 0.627, 0.675),
peach: Color::from_rgb(0.980, 0.702, 0.529),
yellow: Color::from_rgb(0.976, 0.886, 0.686),
green: Color::from_rgb(0.651, 0.890, 0.631),
teal: Color::from_rgb(0.580, 0.886, 0.835),
sky: Color::from_rgb(0.537, 0.863, 0.922),
sapphire: Color::from_rgb(0.455, 0.780, 0.925),
blue: Color::from_rgb(0.537, 0.706, 0.980),
lavender: Color::from_rgb(0.706, 0.745, 0.996),
text: Color::from_rgb(0.804, 0.839, 0.957),
subtext1: Color::from_rgb(0.729, 0.761, 0.871),
subtext0: Color::from_rgb(0.651, 0.678, 0.784),
overlay2: Color::from_rgb(0.576, 0.600, 0.698),
overlay1: Color::from_rgb(0.498, 0.518, 0.612),
overlay0: Color::from_rgb(0.424, 0.439, 0.525),
surface2: Color::from_rgb(0.345, 0.357, 0.439),
surface1: Color::from_rgb(0.271, 0.278, 0.353),
surface0: Color::from_rgb(0.192, 0.196, 0.267),
base: Color::from_rgb(0.118, 0.118, 0.180),
mantle: Color::from_rgb(0.094, 0.094, 0.145),
crust: Color::from_rgb(0.067, 0.067, 0.106),
};
pub static LATTE: Palette = Palette {
rosewater: Color::from_rgb(0.863, 0.541, 0.471),
flamingo: Color::from_rgb(0.867, 0.471, 0.471),
pink: Color::from_rgb(0.918, 0.463, 0.796),
mauve: Color::from_rgb(0.533, 0.224, 0.937),
red: Color::from_rgb(0.824, 0.059, 0.224),
maroon: Color::from_rgb(0.902, 0.271, 0.325),
peach: Color::from_rgb(0.996, 0.392, 0.043),
yellow: Color::from_rgb(0.875, 0.557, 0.114),
green: Color::from_rgb(0.251, 0.627, 0.169),
teal: Color::from_rgb(0.090, 0.573, 0.600),
sky: Color::from_rgb(0.016, 0.647, 0.898),
sapphire: Color::from_rgb(0.125, 0.624, 0.710),
blue: Color::from_rgb(0.118, 0.400, 0.961),
lavender: Color::from_rgb(0.447, 0.529, 0.992),
text: Color::from_rgb(0.298, 0.310, 0.412),
subtext1: Color::from_rgb(0.361, 0.373, 0.467),
subtext0: Color::from_rgb(0.424, 0.435, 0.522),
overlay2: Color::from_rgb(0.486, 0.498, 0.576),
overlay1: Color::from_rgb(0.549, 0.561, 0.631),
overlay0: Color::from_rgb(0.612, 0.627, 0.690),
surface2: Color::from_rgb(0.675, 0.690, 0.745),
surface1: Color::from_rgb(0.737, 0.753, 0.800),
surface0: Color::from_rgb(0.800, 0.816, 0.855),
base: Color::from_rgb(0.937, 0.945, 0.961),
mantle: Color::from_rgb(0.902, 0.914, 0.937),
crust: Color::from_rgb(0.863, 0.878, 0.910),
};
thread_local! {
static CURRENT: RefCell<&'static Palette> = const { RefCell::new(&MOCHA) };
}
pub fn current() -> &'static Palette {
CURRENT.with(|c| *c.borrow())
}
pub fn set_theme(name: &str) {
let pal = match name {
"latte" | "light" => &LATTE,
_ => &MOCHA,
};
CURRENT.with(|c| *c.borrow_mut() = pal);
}

View File

@ -4,6 +4,7 @@ use iced_wgpu::core::text::highlighter;
use iced_wgpu::core::Color;
use acord_core::highlight::{highlight_source, HighlightSpan};
use crate::editor::{RESULT_PREFIX, ERROR_PREFIX};
use crate::palette;
pub const EVAL_RESULT_KIND: u8 = 24;
pub const EVAL_ERROR_KIND: u8 = 25;
@ -107,33 +108,34 @@ impl highlighter::Highlighter for SyntaxHighlighter {
}
pub fn highlight_color(kind: u8) -> Color {
let p = palette::current();
match kind {
0 => Color::from_rgb(0.804, 0.569, 0.945), // keyword - mauve
1 => Color::from_rgb(0.537, 0.706, 0.980), // function - blue
2 => Color::from_rgb(0.604, 0.831, 0.898), // function.builtin - teal
3 => Color::from_rgb(0.976, 0.827, 0.522), // type - yellow
4 => Color::from_rgb(0.976, 0.827, 0.522), // type.builtin - yellow
5 => Color::from_rgb(0.569, 0.878, 0.800), // constructor - teal
6 => Color::from_rgb(0.988, 0.702, 0.529), // constant - peach
7 => Color::from_rgb(0.988, 0.702, 0.529), // constant.builtin - peach
8 => Color::from_rgb(0.651, 0.890, 0.631), // string - green
9 => Color::from_rgb(0.988, 0.702, 0.529), // number - peach
10 => Color::from_rgb(0.424, 0.443, 0.537), // comment - overlay0
11 => Color::from_rgb(0.804, 0.839, 0.957), // variable - text
12 => Color::from_rgb(0.949, 0.604, 0.584), // variable.builtin - red
13 => Color::from_rgb(0.949, 0.773, 0.584), // variable.parameter - flamingo
14 => Color::from_rgb(0.604, 0.831, 0.898), // operator - sky
15 => Color::from_rgb(0.580, 0.612, 0.733), // punctuation - overlay2
16 => Color::from_rgb(0.580, 0.612, 0.733), // punctuation.bracket - overlay2
17 => Color::from_rgb(0.580, 0.612, 0.733), // punctuation.delimiter - overlay2
18 => Color::from_rgb(0.537, 0.706, 0.980), // property - blue
19 => Color::from_rgb(0.804, 0.569, 0.945), // tag - mauve
20 => Color::from_rgb(0.976, 0.827, 0.522), // attribute - yellow
21 => Color::from_rgb(0.569, 0.878, 0.800), // label - teal
22 => Color::from_rgb(0.949, 0.604, 0.584), // escape - red
23 => Color::from_rgb(0.804, 0.839, 0.957), // embedded - text
24 => Color::from_rgb(0.651, 0.890, 0.631), // eval result - green
25 => Color::from_rgb(0.890, 0.400, 0.400), // eval error - muted red
_ => Color::from_rgb(0.804, 0.839, 0.957), // default text
0 => p.mauve,
1 => p.blue,
2 => p.teal,
3 => p.yellow,
4 => p.yellow,
5 => p.teal,
6 => p.peach,
7 => p.peach,
8 => p.green,
9 => p.peach,
10 => p.overlay0,
11 => p.text,
12 => p.red,
13 => p.flamingo,
14 => p.sky,
15 => p.overlay2,
16 => p.overlay2,
17 => p.overlay2,
18 => p.blue,
19 => p.mauve,
20 => p.yellow,
21 => p.teal,
22 => p.red,
23 => p.text,
24 => p.green,
25 => p.maroon,
_ => p.text,
}
}