M7: make brand accents legible and fix home tests

This commit is contained in:
YarilaFox committed 2026-10-19 20:51:47 +05:00
1 parent 4b12a82839
commit 5a4b7c2795
18 files changed
+66 -59

No files matched your search

+1
View File
@@ -25,6 +25,7 @@ jobs:
app-name: figcraft app-name: figcraft
app-title: FigCraft app-title: FigCraft
craft-fonts-ref: 43913056ce82a3fdad0ef69f6b88d1dc922a8104 craft-fonts-ref: 43913056ce82a3fdad0ef69f6b88d1dc922a8104
web-craft-fonts: true
has-server: true has-server: true
version: ${{ inputs.version }} version: ${{ inputs.version }}
secrets: secrets:
+2 -2
View File
@@ -65,8 +65,8 @@ fn craft_fonts(dir: &std::path::Path, manifest_rel: &str) -> Result<String, Stri
if cjk && std::env::var_os("FIGCRAFT_EMBED_CJK").is_none() { if cjk && std::env::var_os("FIGCRAFT_EMBED_CJK").is_none() {
continue; continue;
} }
// Web builds have a size budget: UI fonts, the FigCraft wordmark, and optionally CJK. // Web builds have a size budget: UI fonts, only the SemiBold wordmark, and optionally CJK.
if wasm && *family != "Inter" && *family != "Archivo" && !cjk { if wasm && *family != "Inter" && !(*family == "Archivo" && *style == "SemiBold") && !cjk {
continue; continue;
} }
let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?; let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?;
+1 -1
View File
@@ -316,7 +316,7 @@ fn search_row(app: &mut FigApp, ui: &mut Ui, w: f32) -> String {
); );
crate::widgets::select_all_on_focus(ui, &r, &filter); crate::widgets::select_all_on_focus(ui, &r, &filter);
if r.has_focus() { if r.has_focus() {
ui.painter().rect_stroke(field, CornerRadius::same(5), Stroke::new(1.0, t.accent), egui::StrokeKind::Inside); ui.painter().rect_stroke(field, CornerRadius::same(5), Stroke::new(1.0, crate::theme::ACCENT), egui::StrokeKind::Inside);
if ui.input(|i| i.key_pressed(egui::Key::Escape)) { if ui.input(|i| i.key_pressed(egui::Key::Escape)) {
filter.clear(); filter.clear();
} }
+5 -4
View File
@@ -189,10 +189,10 @@ pub fn cluster(points: &[Pos2], r: f32) -> Vec<Vec<usize>> {
} }
/// A message's text with its @mentions of `people` picked out, wrapped at `width`. /// A message's text with its @mentions of `people` picked out, wrapped at `width`.
pub fn rich_text(text: &str, people: &[&str], color: Color32, width: f32) -> LayoutJob { pub fn rich_text(text: &str, people: &[&str], color: Color32, mention_color: Color32, width: f32) -> LayoutJob {
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
let plain = TextFormat { font_id: regular(12.0), color, ..Default::default() }; let plain = TextFormat { font_id: regular(12.0), color, ..Default::default() };
let mention = TextFormat { font_id: semibold(12.0), color: ACCENT, ..Default::default() }; let mention = TextFormat { font_id: semibold(12.0), color: mention_color, ..Default::default() };
let mut at = 0; let mut at = 0;
for r in mention_ranges(text, people) { for r in mention_ranges(text, people) {
job.append(text.get(at..r.start).unwrap_or(""), 0.0, plain.clone()); job.append(text.get(at..r.start).unwrap_or(""), 0.0, plain.clone());
@@ -729,7 +729,7 @@ fn message(
ui.add_space(indent); ui.add_space(indent);
let mut names: Vec<&str> = cx.people.iter().map(String::as_str).collect(); let mut names: Vec<&str> = cx.people.iter().map(String::as_str).collect();
names.push(cx.me); names.push(cx.me);
ui.add(egui::Label::new(rich_text(&m.text, &names, t.text, ui.available_width())).wrap()); ui.add(egui::Label::new(rich_text(&m.text, &names, t.text, t.accent_text, ui.available_width())).wrap());
}); });
} }
} }
@@ -1062,7 +1062,8 @@ pub fn panel(app: &mut FigApp, ui: &mut Ui) {
ui.add_space(16.0); ui.add_space(16.0);
ui.label(egui::RichText::new(tr(app.ui.comments.filter.label())).font(regular(11.0)).color(t.text_secondary)); ui.label(egui::RichText::new(tr(app.ui.comments.filter.label())).font(regular(11.0)).color(t.text_secondary));
ui.add_space(4.0); ui.add_space(4.0);
if ui.add(egui::Label::new(egui::RichText::new(tr("Show open")).font(regular(11.0)).color(ACCENT)).sense(Sense::click())).clicked() { if ui.add(egui::Label::new(egui::RichText::new(tr("Show open")).font(regular(11.0)).color(t.accent_text)).sense(Sense::click())).clicked()
{
app.ui.comments.filter = Filter::Open; app.ui.comments.filter = Filter::Open;
} }
}); });
+1 -1
View File
@@ -628,7 +628,7 @@ pub fn dev_resources_section(app: &mut FigApp, ui: &mut Ui, doc: &Document, id:
} }
icons::paint(ui.painter(), Rect::from_center_size(pos2(r.min.x + PAD + 7.0, r.center().y), vec2(14.0, 14.0)), Icon::Link, t.icon_secondary); icons::paint(ui.painter(), Rect::from_center_size(pos2(r.min.x + PAD + 7.0, r.center().y), vec2(14.0, 14.0)), Icon::Link, t.icon_secondary);
let x = r.min.x + PAD + 22.0; let x = r.min.x + PAD + 22.0;
ui.painter().text(pos2(x, r.center().y - 7.0), Align2::LEFT_CENTER, widgets::elide(ui, name, text_w), semibold(11.0), ACCENT); ui.painter().text(pos2(x, r.center().y - 7.0), Align2::LEFT_CENTER, widgets::elide(ui, name, text_w), semibold(11.0), t.accent_text);
let below = match item.from.and_then(|f| doc.node(f)) { let below = match item.from.and_then(|f| doc.node(f)) {
Some(m) => trf("From {name}", &[("name", &m.name)]), Some(m) => trf("From {name}", &[("name", &m.name)]),
None => url.clone(), None => url.clone(),
+1 -1
View File
@@ -324,7 +324,7 @@ fn action_card(ui: &mut Ui, t: &Tokens, icon: Icon, title: &str, sub: &str) -> b
} }
card_frame(ui, t, r, &resp); card_frame(ui, t, r, &resp);
let ir = Rect::from_min_size(pos2(r.min.x + 16.0, r.center().y - 16.0), vec2(32.0, 32.0)); let ir = Rect::from_min_size(pos2(r.min.x + 16.0, r.center().y - 16.0), vec2(32.0, 32.0));
ui.painter().rect_filled(ir, CornerRadius::same(6), ACCENT); ui.painter().rect_filled(ir, CornerRadius::same(6), t.accent);
icons::paint(ui.painter(), ir.shrink(8.0), icon, t.accent_on); icons::paint(ui.painter(), ir.shrink(8.0), icon, t.accent_on);
ui.painter().text(pos2(r.min.x + 60.0, r.center().y - 8.0), Align2::LEFT_CENTER, title_shown, semibold(12.0), t.text); ui.painter().text(pos2(r.min.x + 60.0, r.center().y - 8.0), Align2::LEFT_CENTER, title_shown, semibold(12.0), t.text);
ui.painter().text(pos2(r.min.x + 60.0, r.center().y + 9.0), Align2::LEFT_CENTER, sub_shown, regular(11.0), t.text_secondary); ui.painter().text(pos2(r.min.x + 60.0, r.center().y + 9.0), Align2::LEFT_CENTER, sub_shown, regular(11.0), t.text_secondary);
+1 -1
View File
@@ -738,7 +738,7 @@ fn file_icon(ui: &Ui, t: &Tokens, center: egui::Pos2, path: &str) {
let r = Rect::from_center_size(center, vec2(16.0, 16.0)); let r = Rect::from_center_size(center, vec2(16.0, 16.0));
match FileKind::of(Path::new(path)) { match FileKind::of(Path::new(path)) {
FileKind::FigCraft | FileKind::Fig => { FileKind::FigCraft | FileKind::Fig => {
ui.painter().rect_filled(r, CornerRadius::same(4), ACCENT); ui.painter().rect_filled(r, CornerRadius::same(4), t.accent);
icons::paint(ui.painter(), r.shrink(3.0), Icon::Logo, t.accent_on); icons::paint(ui.painter(), r.shrink(3.0), Icon::Logo, t.accent_on);
} }
kind => { kind => {
+4 -2
View File
@@ -256,9 +256,11 @@ fn rects_of(s: &[Shape], c: egui::Color32) -> usize {
s.iter().filter(|s| matches!(s, Shape::Rect(r) if r.fill == c)).count() s.iter().filter(|s| matches!(s, Shape::Rect(r) if r.fill == c)).count()
} }
/// Images drawn (meshes with a texture of their own). /// Preview images in the main content area, excluding the branded sidebar icon.
fn images(s: &[Shape]) -> usize { fn images(s: &[Shape]) -> usize {
s.iter().filter(|s| matches!(s, Shape::Mesh(m) if m.texture_id != egui::TextureId::default())).count() s.iter()
.filter(|s| matches!(s, Shape::Mesh(m) if m.texture_id != egui::TextureId::default() && m.vertices.iter().any(|v| v.pos.x > 240.0)))
.count()
} }
#[test] #[test]
+2 -2
View File
@@ -332,7 +332,7 @@ fn pages_edge(app: &mut FigApp, ui: &mut Ui, shown: f32) {
if active { if active {
ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeVertical); ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeVertical);
} }
let (width, colour) = if active { (2.0, t.accent) } else { (1.0, t.border) }; let (width, colour) = if active { (2.0, ACCENT) } else { (1.0, t.border) };
ui.painter().line_segment([line.left_center(), line.right_center()], Stroke::new(width, colour)); ui.painter().line_segment([line.left_center(), line.right_center()], Stroke::new(width, colour));
let asked = if resp.double_clicked() { let asked = if resp.double_clicked() {
Some(None) Some(None)
@@ -642,7 +642,7 @@ fn search_field(app: &mut FigApp, ui: &mut Ui, t: &Tokens, w: f32, id: Id) {
); );
crate::widgets::select_all_on_focus(ui, &r, &app.ui.layer_filter); crate::widgets::select_all_on_focus(ui, &r, &app.ui.layer_filter);
if r.has_focus() { if r.has_focus() {
ui.painter().rect_stroke(field, CornerRadius::same(5), Stroke::new(1.0, t.accent), egui::StrokeKind::Inside); ui.painter().rect_stroke(field, CornerRadius::same(5), Stroke::new(1.0, ACCENT), egui::StrokeKind::Inside);
} }
if (r.has_focus() || r.lost_focus()) && ui.input(|i| i.key_pressed(egui::Key::Escape)) { if (r.has_focus() || r.lost_focus()) && ui.input(|i| i.key_pressed(egui::Key::Escape)) {
app.ui.layer_search = false; app.ui.layer_search = false;
+3 -3
View File
@@ -1032,7 +1032,7 @@ impl FigApp {
let resp = ui.interact(rect, id, egui::Sense::click_and_drag()); let resp = ui.interact(rect, id, egui::Sense::click_and_drag());
if resp.hovered() || resp.dragged() { if resp.hovered() || resp.dragged() {
ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeHorizontal); ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeHorizontal);
ui.painter().vline(x, rows, egui::Stroke::new(2.0, theme::Tokens::get(ui.ctx()).accent)); ui.painter().vline(x, rows, egui::Stroke::new(2.0, theme::ACCENT));
} }
if resp.double_clicked() { if resp.double_clicked() {
return Some(PANEL_DEFAULT); return Some(PANEL_DEFAULT);
@@ -1716,11 +1716,11 @@ pub(crate) fn drop_overlay(ctx: &egui::Context, rect: egui::Rect, label: &str) {
let painter = ctx.layer_painter(egui::LayerId::new(egui::Order::Foreground, egui::Id::new("drop_overlay"))); let painter = ctx.layer_painter(egui::LayerId::new(egui::Order::Foreground, egui::Id::new("drop_overlay")));
let area = rect.shrink(4.0); let area = rect.shrink(4.0);
painter.rect_filled(area, egui::CornerRadius::same(8), t.accent.gamma_multiply(0.12)); painter.rect_filled(area, egui::CornerRadius::same(8), t.accent.gamma_multiply(0.12));
painter.rect_stroke(area, egui::CornerRadius::same(8), egui::Stroke::new(2.0, t.accent), egui::StrokeKind::Inside); painter.rect_stroke(area, egui::CornerRadius::same(8), egui::Stroke::new(2.0, theme::ACCENT), egui::StrokeKind::Inside);
let galley = painter.layout_no_wrap(label.to_owned(), theme::semibold(14.0), t.text); let galley = painter.layout_no_wrap(label.to_owned(), theme::semibold(14.0), t.text);
let pill = egui::Rect::from_center_size(area.center(), galley.size() + egui::vec2(32.0, 16.0)); let pill = egui::Rect::from_center_size(area.center(), galley.size() + egui::vec2(32.0, 16.0));
painter.rect_filled(pill, egui::CornerRadius::same(8), t.panel); painter.rect_filled(pill, egui::CornerRadius::same(8), t.panel);
painter.rect_stroke(pill, egui::CornerRadius::same(8), egui::Stroke::new(1.0, t.accent), egui::StrokeKind::Inside); painter.rect_stroke(pill, egui::CornerRadius::same(8), egui::Stroke::new(1.0, theme::ACCENT), egui::StrokeKind::Inside);
painter.galley(pill.center() - galley.size() / 2.0, galley, t.text); painter.galley(pill.center() - galley.size() / 2.0, galley, t.text);
// Hovering sends no pointer events on every platform: keep the overlay current. // Hovering sends no pointer events on every platform: keep the overlay current.
ctx.request_repaint(); ctx.request_repaint();
+1 -1
View File
@@ -2223,7 +2223,7 @@ fn top_bar(p: &mut Presenter, ui: &mut Ui, bar: Rect, now: f64, chrome: Chrome)
} }
let on = (*act == BarAction::Comments && p.view.comments) || (*act == BarAction::Inspect && p.view.inspect); let on = (*act == BarAction::Comments && p.view.comments) || (*act == BarAction::Inspect && p.view.inspect);
if on { if on {
ui.painter().rect_filled(br, CornerRadius::same(5), ACCENT); ui.painter().rect_filled(br, CornerRadius::same(5), t.accent);
} else if resp.hovered() { } else if resp.hovered() {
ui.painter().rect_filled(br, CornerRadius::same(5), hover); ui.painter().rect_filled(br, CornerRadius::same(5), hover);
} }
+1 -1
View File
@@ -763,7 +763,7 @@ fn trigger_rows(app: &mut FigApp, ui: &mut Ui, node: NodeId, k: usize, it: &Inte
let id = capture_id().with("field"); let id = capture_id().with("field");
ui.painter().rect_filled(r, CornerRadius::same(5), t.field); ui.painter().rect_filled(r, CornerRadius::same(5), t.field);
if cap { if cap {
ui.painter().rect_stroke(r, CornerRadius::same(5), Stroke::new(1.0, t.accent), StrokeKind::Inside); ui.painter().rect_stroke(r, CornerRadius::same(5), Stroke::new(1.0, ACCENT), StrokeKind::Inside);
ui.memory_mut(|m| m.request_focus(id)); ui.memory_mut(|m| m.request_focus(id));
let _ = ui.interact(r, id, Sense::focusable_noninteractive()); let _ = ui.interact(r, id, Sense::focusable_noninteractive());
let pressed = ui.input(|i| { let pressed = ui.input(|i| {
+4 -4
View File
@@ -11,7 +11,7 @@ use crate::i18n::{tr, trf, trn};
use crate::icons::{self, Icon}; use crate::icons::{self, Icon};
use crate::list_drag::{ListDrag, move_flags, remove_flags}; use crate::list_drag::{ListDrag, move_flags, remove_flags};
use crate::style_picker::{self, StyleSlot}; use crate::style_picker::{self, StyleSlot};
use crate::theme::{ACCENT, Tokens, regular, semibold}; use crate::theme::{Tokens, regular, semibold};
use crate::widgets::{self, Edit, FIELD_H, FieldEdit, Lead, PAD}; use crate::widgets::{self, Edit, FIELD_H, FieldEdit, Lead, PAD};
use crate::{FigApp, RightTab}; use crate::{FigApp, RightTab};
@@ -88,7 +88,7 @@ pub fn right_panel(app: &mut FigApp, ui: &mut Ui) {
let sw = (label.size().x + 24.0).max(56.0); let sw = (label.size().x + 24.0).max(56.0);
let share = Rect::from_min_size(pos2(top.max.x - 14.0 - sw, top.center().y - 16.0), vec2(sw, 32.0)); let share = Rect::from_min_size(pos2(top.max.x - 14.0 - sw, top.center().y - 16.0), vec2(sw, 32.0));
let sresp = ui.interact(share, ui.id().with("share"), Sense::click()).tip(tr("Share: live session, export")); let sresp = ui.interact(share, ui.id().with("share"), Sense::click()).tip(tr("Share: live session, export"));
ui.painter().rect_filled(share, CornerRadius::same(6), if sresp.hovered() { t.accent_hover } else { ACCENT }); ui.painter().rect_filled(share, CornerRadius::same(6), if sresp.hovered() { t.accent_hover } else { t.accent });
ui.painter().galley(share.center() - label.size() / 2.0, label, t.accent_on); ui.painter().galley(share.center() - label.size() / 2.0, label, t.accent_on);
crate::live::share_popover(app, &sresp); crate::live::share_popover(app, &sresp);
// Play presents; its chevron opens the play menu (present, or preview in a pane). // Play presents; its chevron opens the play menu (present, or preview in a pane).
@@ -286,11 +286,11 @@ pub fn menu_row(ui: &mut Ui, label: &str, shortcut: &str, art_w: f32, art: impl
if hovered { if hovered {
ui.painter().rect_filled(r, CornerRadius::same(5), t.menu_hover); ui.painter().rect_filled(r, CornerRadius::same(5), t.menu_hover);
} }
let col = if hovered { Color32::WHITE } else { t.text }; let col = t.menu_text;
ui.painter().galley(pos2(r.min.x + MENU_PAD, r.center().y - text.size().y / 2.0), text, col); ui.painter().galley(pos2(r.min.x + MENU_PAD, r.center().y - text.size().y / 2.0), text, col);
if let Some(k) = keys { if let Some(k) = keys {
let at = pos2(r.max.x - MENU_PAD - k.size().x, r.center().y - k.size().y / 2.0); let at = pos2(r.max.x - MENU_PAD - k.size().x, r.center().y - k.size().y / 2.0);
ui.painter().galley(at, k, if hovered { Color32::WHITE } else { t.text_secondary }); ui.painter().galley(at, k, if hovered { t.menu_text } else { t.menu_dim });
} }
art(ui.painter(), r, col); art(ui.painter(), r, col);
resp resp
+1 -1
View File
@@ -482,7 +482,7 @@ fn style_grid(ui: &mut Ui, list: &[(&Style, Key)], applied: Option<&Key>, editab
None => preview(ui, r, s, StyleSlot::Fill), None => preview(ui, r, s, StyleSlot::Fill),
} }
if applied == Some(key) { if applied == Some(key) {
ui.painter().rect_stroke(r.expand(2.0), CornerRadius::same(5), Stroke::new(2.0, t.accent), egui::StrokeKind::Inside); ui.painter().rect_stroke(r.expand(2.0), CornerRadius::same(5), Stroke::new(2.0, crate::theme::ACCENT), egui::StrokeKind::Inside);
} else if resp.hovered() && !text { } else if resp.hovered() && !text {
ui.painter().rect_stroke(r.expand(2.0), CornerRadius::same(4), Stroke::new(1.0, t.menu_text), egui::StrokeKind::Inside); ui.painter().rect_stroke(r.expand(2.0), CornerRadius::same(4), Stroke::new(1.0, t.menu_text), egui::StrokeKind::Inside);
} }
+22 -19
View File
@@ -45,14 +45,17 @@ pub struct Tokens {
pub focus: Color32, pub focus: Color32,
} }
/// FigCraft's brand green, shared with the original app icon. /// FigCraft's brand green for solid surfaces.
pub const ACCENT: Color32 = Color32::from_rgb(0x02, 0xDA, 0x96); pub const BRAND: Color32 = Color32::from_rgb(0x02, 0xDA, 0x96);
/// Contrast-safe green for thin selection outlines and white-on-green labels.
/// It reaches 4.5:1 on white and 3:1 on the dark canvas.
pub const ACCENT: Color32 = Color32::from_rgb(0x00, 0x87, 0x5F);
/// Ink on the bright green brand surface (white would not have enough contrast). /// Ink on the bright green brand surface (white would not have enough contrast).
pub const ACCENT_ON: Color32 = Color32::from_rgb(0x07, 0x35, 0x2A); pub const ACCENT_ON: Color32 = Color32::from_rgb(0x07, 0x35, 0x2A);
pub const COMPONENT: Color32 = Color32::from_rgb(0x97, 0x47, 0xFF); pub const COMPONENT: Color32 = Color32::from_rgb(0x97, 0x47, 0xFF);
/// Handoff status badges (white text on these, in both themes). /// Handoff status badges (white text on these, in both themes).
pub const DEV_READY: Color32 = Color32::from_rgb(0x14, 0xAE, 0x5C); pub const DEV_READY: Color32 = Color32::from_rgb(0x08, 0x7D, 0x4F);
pub const DEV_CHANGED: Color32 = Color32::from_rgb(0xD9, 0x7A, 0x06); pub const DEV_CHANGED: Color32 = Color32::from_rgb(0xA7, 0x5B, 0x00);
pub const DEV_COMPLETED: Color32 = Color32::from_rgb(0x6E, 0x6E, 0x73); pub const DEV_COMPLETED: Color32 = Color32::from_rgb(0x6E, 0x6E, 0x73);
impl Tokens { impl Tokens {
@@ -74,7 +77,7 @@ impl Tokens {
pressed: Color32::from_rgb(0xE6, 0xE6, 0xE6), pressed: Color32::from_rgb(0xE6, 0xE6, 0xE6),
field: Color32::from_rgb(0xF5, 0xF5, 0xF5), field: Color32::from_rgb(0xF5, 0xF5, 0xF5),
field_hover_border: Color32::from_rgb(0xE6, 0xE6, 0xE6), field_hover_border: Color32::from_rgb(0xE6, 0xE6, 0xE6),
accent: ACCENT, accent: BRAND,
accent_hover: Color32::from_rgb(0x00, 0xAD, 0x78), accent_hover: Color32::from_rgb(0x00, 0xAD, 0x78),
accent_on: ACCENT_ON, accent_on: ACCENT_ON,
// Darker green keeps links and labels legible on white and selected rows. // Darker green keeps links and labels legible on white and selected rows.
@@ -84,7 +87,7 @@ impl Tokens {
component: COMPONENT, component: COMPONENT,
component_row: Color32::from_rgb(0xF1, 0xE9, 0xFF), component_row: Color32::from_rgb(0xF1, 0xE9, 0xFF),
danger: Color32::from_rgb(0xF2, 0x48, 0x22), danger: Color32::from_rgb(0xF2, 0x48, 0x22),
success: Color32::from_rgb(0x14, 0xAE, 0x5C), success: DEV_READY,
warning: Color32::from_rgb(0xFF, 0xCD, 0x29), warning: Color32::from_rgb(0xFF, 0xCD, 0x29),
guide: Color32::from_rgb(0xF2, 0x48, 0x22), guide: Color32::from_rgb(0xF2, 0x48, 0x22),
tab_bar: Color32::from_rgb(0xE8, 0xE8, 0xE8), tab_bar: Color32::from_rgb(0xE8, 0xE8, 0xE8),
@@ -92,7 +95,7 @@ impl Tokens {
shadow: Color32::from_black_alpha(40), shadow: Color32::from_black_alpha(40),
menu: Color32::from_rgb(0x1E, 0x1E, 0x1E), menu: Color32::from_rgb(0x1E, 0x1E, 0x1E),
menu_text: Color32::from_rgb(0xFF, 0xFF, 0xFF), menu_text: Color32::from_rgb(0xFF, 0xFF, 0xFF),
menu_hover: Color32::from_rgb(0x00, 0x6B, 0x4C), menu_hover: ACCENT,
menu_dim: Color32::from_rgb(0x9A, 0x9A, 0x9A), menu_dim: Color32::from_rgb(0x9A, 0x9A, 0x9A),
focus: Color32::from_rgb(0x00, 0x7A, 0x56), focus: Color32::from_rgb(0x00, 0x7A, 0x56),
} }
@@ -115,7 +118,7 @@ impl Tokens {
pressed: Color32::from_rgb(0x44, 0x44, 0x44), pressed: Color32::from_rgb(0x44, 0x44, 0x44),
field: Color32::from_rgb(0x38, 0x38, 0x38), field: Color32::from_rgb(0x38, 0x38, 0x38),
field_hover_border: Color32::from_rgb(0x4D, 0x4D, 0x4D), field_hover_border: Color32::from_rgb(0x4D, 0x4D, 0x4D),
accent: ACCENT, accent: BRAND,
accent_hover: Color32::from_rgb(0x52, 0xE9, 0xB7), accent_hover: Color32::from_rgb(0x52, 0xE9, 0xB7),
accent_on: ACCENT_ON, accent_on: ACCENT_ON,
accent_text: Color32::from_rgb(0x52, 0xE9, 0xB7), accent_text: Color32::from_rgb(0x52, 0xE9, 0xB7),
@@ -124,7 +127,7 @@ impl Tokens {
component: Color32::from_rgb(0xD2, 0x7C, 0xFF), component: Color32::from_rgb(0xD2, 0x7C, 0xFF),
component_row: Color32::from_rgb(0x3E, 0x30, 0x52), component_row: Color32::from_rgb(0x3E, 0x30, 0x52),
danger: Color32::from_rgb(0xF2, 0x48, 0x22), danger: Color32::from_rgb(0xF2, 0x48, 0x22),
success: Color32::from_rgb(0x14, 0xAE, 0x5C), success: DEV_READY,
warning: Color32::from_rgb(0xFF, 0xCD, 0x29), warning: Color32::from_rgb(0xFF, 0xCD, 0x29),
guide: Color32::from_rgb(0xF2, 0x48, 0x22), guide: Color32::from_rgb(0xF2, 0x48, 0x22),
tab_bar: Color32::from_rgb(0x1E, 0x1E, 0x1E), tab_bar: Color32::from_rgb(0x1E, 0x1E, 0x1E),
@@ -132,9 +135,9 @@ impl Tokens {
shadow: Color32::from_black_alpha(90), shadow: Color32::from_black_alpha(90),
menu: Color32::from_rgb(0x1E, 0x1E, 0x1E), menu: Color32::from_rgb(0x1E, 0x1E, 0x1E),
menu_text: Color32::from_rgb(0xFF, 0xFF, 0xFF), menu_text: Color32::from_rgb(0xFF, 0xFF, 0xFF),
menu_hover: Color32::from_rgb(0x00, 0x6B, 0x4C), menu_hover: ACCENT,
menu_dim: Color32::from_rgb(0x9A, 0x9A, 0x9A), menu_dim: Color32::from_rgb(0x9A, 0x9A, 0x9A),
focus: ACCENT, focus: BRAND,
} }
} }
@@ -192,7 +195,7 @@ fn visuals(t: Tokens) -> Visuals {
v.window_corner_radius = egui::CornerRadius::same(13); v.window_corner_radius = egui::CornerRadius::same(13);
v.menu_corner_radius = egui::CornerRadius::same(13); v.menu_corner_radius = egui::CornerRadius::same(13);
v.selection.bg_fill = t.accent.gamma_multiply(0.3); v.selection.bg_fill = t.accent.gamma_multiply(0.3);
v.selection.stroke = Stroke::new(1.0, t.accent); v.selection.stroke = Stroke::new(1.0, ACCENT);
v.hyperlink_color = t.accent_text; v.hyperlink_color = t.accent_text;
for w in [&mut v.widgets.inactive, &mut v.widgets.hovered, &mut v.widgets.active, &mut v.widgets.open, &mut v.widgets.noninteractive] { for w in [&mut v.widgets.inactive, &mut v.widgets.hovered, &mut v.widgets.active, &mut v.widgets.open, &mut v.widgets.noninteractive] {
w.corner_radius = egui::CornerRadius::same(5); w.corner_radius = egui::CornerRadius::same(5);
@@ -208,10 +211,10 @@ fn visuals(t: Tokens) -> Visuals {
v.widgets.hovered.bg_stroke = Stroke::new(1.0, t.field_hover_border); v.widgets.hovered.bg_stroke = Stroke::new(1.0, t.field_hover_border);
v.widgets.active.bg_fill = t.pressed; v.widgets.active.bg_fill = t.pressed;
v.widgets.active.weak_bg_fill = t.pressed; v.widgets.active.weak_bg_fill = t.pressed;
v.widgets.active.bg_stroke = Stroke::new(1.0, t.accent); v.widgets.active.bg_stroke = Stroke::new(1.0, ACCENT);
v.popup_shadow = egui::Shadow { offset: [0, 4], blur: 16, spread: 0, color: t.shadow }; v.popup_shadow = egui::Shadow { offset: [0, 4], blur: 16, spread: 0, color: t.shadow };
v.window_shadow = egui::Shadow { offset: [0, 8], blur: 24, spread: 0, color: t.shadow }; v.window_shadow = egui::Shadow { offset: [0, 8], blur: 24, spread: 0, color: t.shadow };
v.text_cursor.stroke = Stroke::new(1.0, t.accent); v.text_cursor.stroke = Stroke::new(1.0, ACCENT);
v v
} }
@@ -261,9 +264,9 @@ pub fn install_fonts(ctx: &Context, system: bool, cjk: u8) {
fonts.families.insert(FontFamily::Name("semibold".into()), semi); fonts.families.insert(FontFamily::Name("semibold".into()), semi);
// Keep the logo's typeface independent of UI text. System Archivo is used when available; // Keep the logo's typeface independent of UI text. System Archivo is used when available;
// releases with craft-fonts embed it so the wordmark does not depend on installation. // releases with craft-fonts embed it so the wordmark does not depend on installation.
let archivo = find("Archivo", "SemiBold") let archivo = find("Archivo", "SemiBold").map(FontData::from_static).or_else(|| {
.map(FontData::from_static) system.then(|| figcraft_text::find_face("Archivo", "SemiBold")).flatten().filter(|face| face.family == "Archivo").map(|face| font_data(&face))
.or_else(|| system.then(|| figcraft_text::find_face("Archivo", "SemiBold")).flatten().map(|face| font_data(&face))); });
let mut brand = Vec::new(); let mut brand = Vec::new();
if let Some(data) = archivo { if let Some(data) = archivo {
fonts.font_data.insert("archivo".into(), Arc::new(data)); fonts.font_data.insert("archivo".into(), Arc::new(data));
@@ -368,7 +371,7 @@ pub fn paint_wordmark(painter: &egui::Painter, left_center: egui::Pos2, size: f3
let fig = painter.layout_no_wrap("Fig".into(), font.clone(), t.text); let fig = painter.layout_no_wrap("Fig".into(), font.clone(), t.text);
let fig_width = fig.size().x; let fig_width = fig.size().x;
painter.galley(egui::pos2(left_center.x, left_center.y - fig.size().y / 2.0), fig, t.text); painter.galley(egui::pos2(left_center.x, left_center.y - fig.size().y / 2.0), fig, t.text);
painter.text(egui::pos2(left_center.x + fig_width, left_center.y), egui::Align2::LEFT_CENTER, "Craft", font, t.accent); painter.text(egui::pos2(left_center.x + fig_width, left_center.y), egui::Align2::LEFT_CENTER, "Craft", font, t.accent_text);
} }
/// Paint the owner's original chameleon icon from the existing app asset. /// Paint the owner's original chameleon icon from the existing app asset.
@@ -379,7 +382,7 @@ pub fn paint_app_icon(ui: &egui::Ui, rect: egui::Rect) {
Some(egui::ColorImage::from_rgba_unmultiplied([rgba.width() as usize, rgba.height() as usize], rgba.as_raw())) Some(egui::ColorImage::from_rgba_unmultiplied([rgba.width() as usize, rgba.height() as usize], rgba.as_raw()))
}); });
let Some(image) = image else { let Some(image) = image else {
ui.painter().rect_filled(rect, egui::CornerRadius::same(5), ACCENT); ui.painter().rect_filled(rect, egui::CornerRadius::same(5), BRAND);
crate::icons::paint(ui.painter(), rect.shrink(rect.width() * 0.22), crate::icons::Icon::Logo, ACCENT_ON); crate::icons::paint(ui.painter(), rect.shrink(rect.width() * 0.22), crate::icons::Icon::Logo, ACCENT_ON);
return; return;
}; };
+4 -4
View File
@@ -9,7 +9,7 @@ use crate::FigApp;
use crate::a11y::{self, Nav, Region, Tip}; use crate::a11y::{self, Nav, Region, Tip};
use crate::i18n::{tr, tr_noop, trf}; use crate::i18n::{tr, tr_noop, trf};
use crate::icons::{self, Icon}; use crate::icons::{self, Icon};
use crate::theme::{ACCENT, Tokens, regular}; use crate::theme::{Tokens, regular};
struct Group { struct Group {
key: &'static str, key: &'static str,
@@ -145,7 +145,7 @@ fn main_toolbar(app: &mut FigApp, ui: &mut Ui, canvas: Rect, t: &Tokens) {
// The Actions button reads on while its panel is open; clicking it again closes it. // The Actions button reads on while its panel is open; clicking it again closes it.
let actions_open = id == "actions" && app.ui.palette.is_some(); let actions_open = id == "actions" && app.ui.palette.is_some();
if actions_open { if actions_open {
ui.painter().rect_filled(r, CornerRadius::same(5), ACCENT); ui.painter().rect_filled(r, CornerRadius::same(5), t.accent);
} else if resp.hovered() { } else if resp.hovered() {
ui.painter().rect_filled(r, CornerRadius::same(5), t.hover); ui.painter().rect_filled(r, CornerRadius::same(5), t.hover);
} }
@@ -372,7 +372,7 @@ fn tool_cell(ui: &mut Ui, t: &Tokens, r: Rect, id: impl egui::AsIdSalt, icon: Ic
}); });
a11y::toggle(&resp, tip, active); a11y::toggle(&resp, tip, active);
let bg = if active { let bg = if active {
ACCENT t.accent
} else if resp.hovered() { } else if resp.hovered() {
t.hover t.hover
} else { } else {
@@ -395,7 +395,7 @@ fn done_button(ui: &mut Ui, t: &Tokens, x: f32, cy: f32, tip: &str) -> bool {
divider(ui, t, x, cy); divider(ui, t, x, cy);
let done = Rect::from_min_size(pos2(x + 12.0, cy - 14.0), vec2(DONE_W, 28.0)); let done = Rect::from_min_size(pos2(x + 12.0, cy - 14.0), vec2(DONE_W, 28.0));
let resp = ui.interact(done, ui.id().with("tool_done"), Sense::click()).tip(tip); let resp = ui.interact(done, ui.id().with("tool_done"), Sense::click()).tip(tip);
ui.painter().rect_filled(done, CornerRadius::same(6), if resp.hovered() { t.accent_hover } else { ACCENT }); ui.painter().rect_filled(done, CornerRadius::same(6), if resp.hovered() { t.accent_hover } else { t.accent });
ui.painter().text(done.center(), Align2::CENTER_CENTER, tr("Done"), crate::theme::semibold(11.5), t.accent_on); ui.painter().text(done.center(), Align2::CENTER_CENTER, tr("Done"), crate::theme::semibold(11.5), t.accent_on);
resp.clicked() resp.clicked()
} }
+7 -7
View File
@@ -587,7 +587,7 @@ fn decoration_details(app: &mut FigApp, ui: &mut Ui, styles: &[TextStyle], apply
if !auto if !auto
&& ui && ui
.add( .add(
egui::Label::new(egui::RichText::new(tr("Auto")).font(regular(11.0)).color(Tokens::get(ui.ctx()).accent)) egui::Label::new(egui::RichText::new(tr("Auto")).font(regular(11.0)).color(Tokens::get(ui.ctx()).accent_text))
.sense(Sense::click()), .sense(Sense::click()),
) )
.on_hover_text(tip) .on_hover_text(tip)
@@ -713,7 +713,7 @@ fn variable_axes(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], styles: &[TextSt
} }
if styles.iter().any(|s| !s.variations.is_empty()) { if styles.iter().any(|s| !s.variations.is_empty()) {
ui.add_space(8.0); ui.add_space(8.0);
let r = ui.add(egui::Label::new(egui::RichText::new(tr("Reset to style")).font(regular(11.0)).color(t.accent)).sense(Sense::click())); let r = ui.add(egui::Label::new(egui::RichText::new(tr("Reset to style")).font(regular(11.0)).color(t.accent_text)).sense(Sense::click()));
if r.on_hover_text(tr("Back to the values the font style implies")).clicked() { if r.on_hover_text(tr("Back to the values the font style implies")).clicked() {
app.run("text.setVariations", json!({"ids": ids, "variations": {}, "replace": true})); app.run("text.setVariations", json!({"ids": ids, "variations": {}, "replace": true}));
} }
@@ -789,7 +789,7 @@ fn axis_slider(ui: &mut Ui, id: Id, axis: &figcraft_text::Axis, value: Option<f6
let x = x_of(v); let x = x_of(v);
p.rect_filled(Rect::from_min_max(track.min, pos2(x, track.max.y)), CornerRadius::same(2), t.accent); p.rect_filled(Rect::from_min_max(track.min, pos2(x, track.max.y)), CornerRadius::same(2), t.accent);
p.circle_filled(pos2(x, track.center().y), 6.0, Color32::WHITE); p.circle_filled(pos2(x, track.center().y), 6.0, Color32::WHITE);
let ring = if resp.hovered() || resp.dragged() { t.accent } else { t.border_strong }; let ring = if resp.hovered() || resp.dragged() { crate::theme::ACCENT } else { t.border_strong };
p.circle_stroke(pos2(x, track.center().y), 6.0, Stroke::new(1.0, ring)); p.circle_stroke(pos2(x, track.center().y), 6.0, Stroke::new(1.0, ring));
} }
if resp.double_clicked() { if resp.double_clicked() {
@@ -899,14 +899,14 @@ fn feature_row(ui: &mut Ui, tag: &str, label: &str, on: Option<bool>, supported:
match on { match on {
Some(true) => { Some(true) => {
ui.painter().rect_filled(b, CornerRadius::same(3), t.accent); ui.painter().rect_filled(b, CornerRadius::same(3), t.accent);
icons::paint(ui.painter(), b.shrink(1.5), Icon::Check, Color32::WHITE); icons::paint(ui.painter(), b.shrink(1.5), Icon::Check, t.accent_on);
} }
Some(false) => { Some(false) => {
ui.painter().rect_stroke(b, CornerRadius::same(3), Stroke::new(1.0, t.border_strong), StrokeKind::Inside); ui.painter().rect_stroke(b, CornerRadius::same(3), Stroke::new(1.0, t.border_strong), StrokeKind::Inside);
} }
None => { None => {
ui.painter().rect_filled(b, CornerRadius::same(3), t.accent); ui.painter().rect_filled(b, CornerRadius::same(3), t.accent);
ui.painter().line_segment([pos2(b.min.x + 3.5, b.center().y), pos2(b.max.x - 3.5, b.center().y)], Stroke::new(1.5, Color32::WHITE)); ui.painter().line_segment([pos2(b.min.x + 3.5, b.center().y), pos2(b.max.x - 3.5, b.center().y)], Stroke::new(1.5, t.accent_on));
} }
} }
let col = if supported { t.text } else { t.text_tertiary }; let col = if supported { t.text } else { t.text_tertiary };
@@ -1025,7 +1025,7 @@ fn lead_field(ui: &mut Ui, id: Id, glyph: LeadGlyph, value: &str, width: f32, ti
crate::a11y::text(&resp, tip, value); crate::a11y::text(&resp, tip, value);
ui.painter().rect_filled(rect, CornerRadius::same(5), t.field); ui.painter().rect_filled(rect, CornerRadius::same(5), t.field);
if editing.is_some() { if editing.is_some() {
ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.accent), StrokeKind::Inside); ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, crate::theme::ACCENT), StrokeKind::Inside);
} else if resp.hovered() { } else if resp.hovered() {
ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.field_hover_border), StrokeKind::Inside); ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.field_hover_border), StrokeKind::Inside);
} }
@@ -1274,7 +1274,7 @@ pub(crate) fn font_picker(ui: &mut Ui, current: Option<&str>, just_opened: bool)
if hovered { if hovered {
ui.painter().rect_filled(rect, CornerRadius::same(5), t.accent); ui.painter().rect_filled(rect, CornerRadius::same(5), t.accent);
} }
let col = if hovered { Color32::WHITE } else { t.text }; let col = if hovered { t.accent_on } else { t.text };
if current.is_some_and(|c| c.eq_ignore_ascii_case(f)) { if current.is_some_and(|c| c.eq_ignore_ascii_case(f)) {
icons::paint( icons::paint(
ui.painter(), ui.painter(),
+5 -5
View File
@@ -217,7 +217,7 @@ fn number_field_impl(
let hovered = field_resp.hovered() || lead_resp.hovered() || editing.is_some(); let hovered = field_resp.hovered() || lead_resp.hovered() || editing.is_some();
ui.painter().rect_filled(rect, CornerRadius::same(5), t.field); ui.painter().rect_filled(rect, CornerRadius::same(5), t.field);
if editing.is_some() { if editing.is_some() {
ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.accent), StrokeKind::Inside); ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, crate::theme::ACCENT), StrokeKind::Inside);
} else if hovered { } else if hovered {
ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.field_hover_border), StrokeKind::Inside); ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.field_hover_border), StrokeKind::Inside);
} }
@@ -507,7 +507,7 @@ fn text_field_in(ui: &mut Ui, id: Id, value: &str, rect: Rect, placeholder: &str
a11y::text(&resp, name, value); a11y::text(&resp, name, value);
ui.painter().rect_filled(rect, CornerRadius::same(5), t.field); ui.painter().rect_filled(rect, CornerRadius::same(5), t.field);
if editing.is_some() { if editing.is_some() {
ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.accent), StrokeKind::Inside); ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, crate::theme::ACCENT), StrokeKind::Inside);
} else if resp.hovered() { } else if resp.hovered() {
ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.field_hover_border), StrokeKind::Inside); ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.field_hover_border), StrokeKind::Inside);
} }
@@ -570,7 +570,7 @@ pub fn text_input(ui: &mut Ui, id: Id, text: &mut String, width: f32, hint: &str
ui.painter().rect_filled(rect, CornerRadius::same(5), t.field); ui.painter().rect_filled(rect, CornerRadius::same(5), t.field);
let r = field_editor(ui, rect, id, text, hint); let r = field_editor(ui, rect, id, text, hint);
if r.has_focus() { if r.has_focus() {
ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.accent), StrokeKind::Inside); ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, crate::theme::ACCENT), StrokeKind::Inside);
} else if ui.rect_contains_pointer(rect) { } else if ui.rect_contains_pointer(rect) {
ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.field_hover_border), StrokeKind::Inside); ui.painter().rect_stroke(rect, CornerRadius::same(5), Stroke::new(1.0, t.field_hover_border), StrokeKind::Inside);
} }
@@ -992,7 +992,7 @@ pub fn radio(ui: &mut Ui, on: bool, label: &str) -> bool {
ui.painter().circle_filled(c, 7.0, t.accent); ui.painter().circle_filled(c, 7.0, t.accent);
ui.painter().circle_filled(c, 2.5, t.accent_on); ui.painter().circle_filled(c, 2.5, t.accent_on);
} else { } else {
ui.painter().circle_stroke(c, 6.5, Stroke::new(1.0, if resp.hovered() { t.accent } else { t.border_strong })); ui.painter().circle_stroke(c, 6.5, Stroke::new(1.0, if resp.hovered() { crate::theme::ACCENT } else { t.border_strong }));
} }
ui.painter().text(pos2(rect.min.x + 22.0, rect.center().y), Align2::LEFT_CENTER, label, regular(11.0), t.text); ui.painter().text(pos2(rect.min.x + 22.0, rect.center().y), Align2::LEFT_CENTER, label, regular(11.0), t.text);
resp.clicked() resp.clicked()
@@ -1046,7 +1046,7 @@ pub fn text_field_inline(ui: &mut Ui, id: Id, value: &str, rect: Rect, font: egu
match buf { match buf {
Some(mut text) => { Some(mut text) => {
ui.painter().rect_filled(rect.expand(2.0), CornerRadius::same(4), t.panel); ui.painter().rect_filled(rect.expand(2.0), CornerRadius::same(4), t.panel);
ui.painter().rect_stroke(rect.expand(2.0), CornerRadius::same(4), Stroke::new(1.0, t.accent), StrokeKind::Inside); ui.painter().rect_stroke(rect.expand(2.0), CornerRadius::same(4), Stroke::new(1.0, crate::theme::ACCENT), StrokeKind::Inside);
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(rect)); let mut child = ui.new_child(egui::UiBuilder::new().max_rect(rect));
let r = crate::bidi::single_line(&mut child, te_id, &mut text, font, rect.width()); let r = crate::bidi::single_line(&mut child, te_id, &mut text, font, rect.width());
a11y::own_focus(&r); a11y::own_focus(&r);