mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
Merge pull request #2331 from storytold/ui/restyle
UI: restyled interface
This commit is contained in:
@@ -265,6 +265,13 @@ pub const PREF_SPECS: &[PrefSpec] = &[
|
||||
"Brightness",
|
||||
choice(&[("dark", "Dark"), ("mediumDark", "Medium Dark"), ("mediumLight", "Medium Light"), ("light", "Light")])
|
||||
),
|
||||
p!(
|
||||
"uiAccent",
|
||||
"User Interface",
|
||||
"",
|
||||
"Accent Color",
|
||||
choice(&[("coral", "Coral"), ("blue", "Blue"), ("violet", "Violet"), ("green", "Green"), ("amber", "Amber"), ("pink", "Pink")])
|
||||
),
|
||||
p!(
|
||||
"canvasColor",
|
||||
"User Interface",
|
||||
|
||||
@@ -744,6 +744,8 @@ pub struct Prefs {
|
||||
pub scratch_secondary: String,
|
||||
// User Interface
|
||||
pub ui_brightness: String,
|
||||
/// User Interface › Accent Color: the colour of what is on, chosen or focused.
|
||||
pub ui_accent: String,
|
||||
pub canvas_color: String,
|
||||
pub auto_collapse_icon_panels: bool,
|
||||
/// User Interface › Show Tool Group Labels: the toolbar's group names (Select, Shapes, Draw…);
|
||||
@@ -976,6 +978,7 @@ impl Default for Prefs {
|
||||
scratch_primary: s("Startup"),
|
||||
scratch_secondary: s("None"),
|
||||
ui_brightness: s("mediumDark"),
|
||||
ui_accent: s("coral"),
|
||||
canvas_color: s("matchUi"),
|
||||
auto_collapse_icon_panels: false,
|
||||
tool_group_labels: false,
|
||||
|
||||
@@ -12,8 +12,9 @@ const DISCORD: Color32 = Color32::from_rgb(0x58, 0x65, 0xF2);
|
||||
|
||||
/// The Discord button's label, height and width.
|
||||
fn discord_layout(ui: &Ui, large: bool) -> (std::sync::Arc<egui::Galley>, f32, f32) {
|
||||
let (h, font, label) = if large { (36.0, theme::semibold(14.0), tl!("Join our Discord")) } else { (24.0, theme::semibold(12.0), "Discord") };
|
||||
let galley = ui.painter().layout_no_wrap(label.to_string(), font, Color32::WHITE);
|
||||
let (h, font, label) =
|
||||
if large { (36.0, theme::semibold(14.0), tl!("Join our Discord")) } else { (24.0, egui::FontId::proportional(12.0), "Discord") };
|
||||
let galley = ui.painter().layout_no_wrap(label.to_string(), font, Color32::PLACEHOLDER);
|
||||
let w = galley.size().x + h * 0.55 + h * 0.9;
|
||||
(galley, h, w)
|
||||
}
|
||||
@@ -23,16 +24,25 @@ pub fn discord_width(ui: &Ui, large: bool) -> f32 {
|
||||
discord_layout(ui, large).2
|
||||
}
|
||||
|
||||
/// A Discord pill button: compact for the header, large for About / Home.
|
||||
/// The Discord button: compact and quiet for the app bar, large for About / Home.
|
||||
pub fn discord_button(app: &mut VectorcraftApp, ui: &mut Ui, large: bool) -> egui::Response {
|
||||
let (galley, h, w) = discord_layout(ui, large);
|
||||
let icon = h * 0.55;
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(w, h), Sense::click());
|
||||
let fill = if resp.hovered() { DISCORD.gamma_multiply(0.85) } else { DISCORD };
|
||||
ui.painter().rect_filled(r, CornerRadius::same((h / 2.0) as u8), fill);
|
||||
let at = egui::pos2(0.0, r.center().y - galley.size().y / 2.0);
|
||||
let ir = egui::Rect::from_center_size(r.left_center() + vec2(h * 0.35 + icon / 2.0, 0.0), vec2(icon, icon));
|
||||
icons::paint(ui, "message-circle", ir, Color32::WHITE);
|
||||
ui.painter().galley(egui::pos2(ir.right() + h * 0.2, r.center().y - galley.size().y / 2.0), galley, Color32::WHITE);
|
||||
if large {
|
||||
let fill = if resp.hovered() { DISCORD.gamma_multiply(0.85) } else { DISCORD };
|
||||
ui.painter().rect_filled(r, CornerRadius::same((h / 2.0) as u8), fill);
|
||||
icons::paint(ui, "message-circle", ir, Color32::WHITE);
|
||||
ui.painter().galley(egui::pos2(ir.right() + h * 0.2, at.y), galley, Color32::WHITE);
|
||||
} else {
|
||||
// In the app bar it stays quiet: a soft button with Discord's colour on its icon alone.
|
||||
let t = Tokens::get(ui.ctx());
|
||||
ui.painter().rect_filled(r, CornerRadius::same(6), if resp.hovered() { t.hover } else { t.button });
|
||||
icons::paint(ui, "message-circle", ir, DISCORD);
|
||||
ui.painter().galley(egui::pos2(ir.right() + h * 0.2, at.y), galley, if resp.hovered() { t.text_strong } else { t.text });
|
||||
}
|
||||
let resp = resp.on_hover_text(tl!("Join the ArtCraft community on Discord (discord.gg/artcraft)"));
|
||||
if resp.clicked() {
|
||||
app.open_link("help.discord");
|
||||
|
||||
@@ -364,7 +364,7 @@ fn value_fields(ui: &mut Ui, p: &Picked, channel: Channel) -> (Option<Picked>, O
|
||||
};
|
||||
// The hex field is taller than a value field. K's row takes the height of the hex
|
||||
// field's row beside it.
|
||||
let height = if i == 6 { left.response.rect.bottom() - ui.cursor().top() } else { ui.spacing().interact_size.y };
|
||||
let height = if i == 6 { (left.response.rect.bottom() - ui.cursor().top()).round() } else { ui.spacing().interact_size.y };
|
||||
let size = vec2(ui.available_size_before_wrap().x, height);
|
||||
ui.allocate_ui_with_layout(size, egui::Layout::left_to_right(egui::Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
|
||||
+27
-28
@@ -46,7 +46,6 @@ pub fn set_collapsed(app: &mut VectorcraftApp, collapsed: bool) {
|
||||
pub(crate) fn collapse_header(ui: &mut Ui, collapsed: bool) -> bool {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let (hdr, resp) = ui.allocate_exact_size(vec2(ui.available_width(), HEADER), Sense::click());
|
||||
ui.painter().rect_filled(hdr, 0.0, t.tab_strip);
|
||||
let arrow = Rect::from_min_size(hdr.right_top() + vec2(-16.0, 1.0), vec2(12.0, 12.0));
|
||||
if resp.hovered() {
|
||||
ui.painter().rect_filled(arrow, CornerRadius::same(2), t.hover);
|
||||
@@ -90,22 +89,27 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
.resizable(true)
|
||||
.default_size(DOCK_WIDTH)
|
||||
.size_range(230.0..=520.0)
|
||||
.frame(egui::Frame::NONE.fill(t.panel).stroke(Stroke::new(1.5, t.border)))
|
||||
.frame(egui::Frame::NONE)
|
||||
.show(ui, |ui| {
|
||||
if collapse_header(ui, false) {
|
||||
toggle(app, true);
|
||||
}
|
||||
if tab_strip(app, ui, active) {
|
||||
// It floats from this frame on.
|
||||
return;
|
||||
}
|
||||
egui::Frame::NONE.inner_margin(egui::Margin { left: 12, right: 10, top: 10, bottom: 8 }).show(ui, |ui| match active {
|
||||
DockTab::Properties => {
|
||||
egui::ScrollArea::vertical().id_salt("props").auto_shrink([false, false]).show(ui, |ui| panels::properties::show(app, ui));
|
||||
crate::theme::card(ui, &t, egui::Margin { left: 0, right: 6, top: 6, bottom: 6 }, |ui| {
|
||||
if collapse_header(ui, false) {
|
||||
toggle(app, true);
|
||||
}
|
||||
DockTab::Layers => panels::layers::show(app, ui),
|
||||
DockTab::Libraries => panels::libraries(app, ui),
|
||||
});
|
||||
if tab_strip(app, ui, active) {
|
||||
// It floats from this frame on.
|
||||
return;
|
||||
}
|
||||
egui::Frame::NONE.inner_margin(egui::Margin { left: 12, right: 10, top: 10, bottom: 8 }).show(ui, |ui| match active {
|
||||
DockTab::Properties => {
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt("props")
|
||||
.auto_shrink([false, false])
|
||||
.show(ui, |ui| panels::properties::show(app, ui));
|
||||
}
|
||||
DockTab::Layers => panels::layers::show(app, ui),
|
||||
DockTab::Libraries => panels::libraries(app, ui),
|
||||
});
|
||||
})
|
||||
})
|
||||
.response
|
||||
.rect
|
||||
@@ -114,16 +118,13 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
// collapsed group's panels head it, under the « that expands them again. Floating panels leave
|
||||
// it.
|
||||
let collapsed = app.ui.dock_collapsed && shown.is_some();
|
||||
let column = egui::Panel::right("icon_column")
|
||||
.resizable(false)
|
||||
.exact_size(ICON_COL)
|
||||
.frame(egui::Frame::NONE.fill(t.panel).stroke(Stroke::new(1.5, t.border)))
|
||||
.show(ui, |ui| {
|
||||
let column = egui::Panel::right("icon_column").resizable(false).exact_size(ICON_COL).frame(egui::Frame::NONE).show(ui, |ui| {
|
||||
crate::theme::card(ui, &t, egui::Margin { left: 3, right: 3, top: 6, bottom: 6 }, |ui| {
|
||||
let bounds = ui.max_rect();
|
||||
if collapsed && collapse_header(ui, true) {
|
||||
toggle(app, false);
|
||||
}
|
||||
egui::Frame::NONE.inner_margin(egui::Margin::symmetric(4, 6)).show(ui, |ui| {
|
||||
egui::Frame::NONE.inner_margin(egui::Margin::symmetric(1, 4)).show(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing.y = 2.0;
|
||||
// The icons scroll in a window too short for them.
|
||||
widgets::strip_scroll(ui, "icon_column", |ui| {
|
||||
@@ -154,7 +155,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
let column = column.response.rect;
|
||||
let dock = group.map_or(column, |g| g.union(column));
|
||||
// Floating sets docked as columns, left of the icon column.
|
||||
@@ -173,8 +175,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
fn tab_strip(app: &mut VectorcraftApp, ui: &mut Ui, active: DockTab) -> bool {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let (strip, _) = ui.allocate_exact_size(vec2(ui.available_width(), 33.0), Sense::hover());
|
||||
ui.painter().rect_filled(strip, 0.0, t.tab_strip);
|
||||
ui.painter().line_segment([strip.left_bottom(), strip.right_bottom()], Stroke::new(1.0, t.border));
|
||||
ui.painter().line_segment([strip.left_bottom(), strip.right_bottom()], Stroke::new(1.0, t.divider));
|
||||
let outside = ui.input(|i| i.pointer.interact_pos()).is_some_and(|p| !strip.contains(p));
|
||||
// What a drag out of the strip floats: the panels, the one shown, where its tab began.
|
||||
let mut tear: Option<(Vec<&'static str>, &'static str, f32)> = None;
|
||||
@@ -264,12 +265,10 @@ pub(crate) fn panel_tabs(
|
||||
.focus_stroke(Stroke::new(1.0, t.accent))
|
||||
.show_at(ui, rect, |ui, _| {
|
||||
if i == selected {
|
||||
ui.painter().rect_filled(rect, 0.0, t.panel);
|
||||
ui.painter().rect_filled(rect.shrink2(vec2(3.0, 5.0)), 6.0, t.button);
|
||||
}
|
||||
ui.painter().with_clip_rect(rect.intersect(ui.clip_rect())).galley(rect.center() - galley.size() / 2.0, galley, t.text);
|
||||
if separators {
|
||||
ui.painter().line_segment([rect.right_top(), rect.right_bottom()], Stroke::new(1.0, t.border));
|
||||
}
|
||||
let _ = separators;
|
||||
});
|
||||
out.responses.push((i, if shrunk { resp.on_hover_text(*label) } else { resp }));
|
||||
out.right = rect.right();
|
||||
|
||||
@@ -4914,3 +4914,9 @@ theme Appearance Външен вид
|
||||
# View › Hide Template / Show Template
|
||||
Hide Template Скриване на шаблона
|
||||
Show Template Показване на шаблона
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Цвят на акцента
|
||||
Amber Кехлибарен
|
||||
Coral Коралов
|
||||
Violet Виолетов
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4932,3 +4932,9 @@ mark-style Japanese Japonské
|
||||
@msg choose a source folder vyberte zdrojovou složku
|
||||
@msg open a document with data sets otevřete dokument se sadami dat
|
||||
@msg the action has no steps akce nemá žádné kroky
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Barva zvýraznění
|
||||
Amber Jantarová
|
||||
Coral Korálová
|
||||
Violet Fialová
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4468,3 +4468,10 @@ theme Appearance Erscheinungsbild
|
||||
@msg choose a source folder wählen Sie einen Quellordner
|
||||
@msg open a document with data sets öffnen Sie ein Dokument mit Datensätzen
|
||||
@msg the action has no steps die Aktion hat keine Schritte
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Akzentfarbe
|
||||
Amber Bernstein
|
||||
Coral Koralle
|
||||
Violet Violett
|
||||
Pink Pink
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4884,3 +4884,9 @@ effect Points Σημεία
|
||||
@msg this is a PostScript file (an .ai saved in an older format or without PDF compatibility, or an EPS file), not a PDF: File › Open and File › Place read it as EPS αυτό είναι αρχείο PostScript (ένα .ai αποθηκευμένο σε παλαιότερη μορφή ή χωρίς συμβατότητα PDF, ή αρχείο EPS), όχι PDF: οι εντολές Αρχείο › Άνοιγμα και Αρχείο › Τοποθέτηση το διαβάζουν ως EPS
|
||||
@msg this system has no screen color sampler αυτό το σύστημα δεν έχει δειγματοληψία χρώματος από την οθόνη
|
||||
@msg the image can't be read as a texture η εικόνα δεν μπορεί να διαβαστεί ως υφή
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Χρώμα έμφασης
|
||||
Amber Κεχριμπαρί
|
||||
Coral Κοραλλί
|
||||
Violet Βιολετί
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 1857 and column 19.
|
@@ -4874,3 +4874,9 @@ mark-style Japanese Japonesas
|
||||
@msg choose a source folder elija una carpeta de origen
|
||||
@msg open a document with data sets abra un documento con conjuntos de datos
|
||||
@msg the action has no steps la acción no tiene pasos
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Color de acento
|
||||
Amber Ámbar
|
||||
Coral Coral
|
||||
Violet Violeta
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4896,3 +4896,9 @@ mark-style Japanese Japanilainen
|
||||
@msg this is a PostScript file (an .ai saved in an older format or without PDF compatibility, or an EPS file), not a PDF: File › Open and File › Place read it as EPS tämä on PostScript-tiedosto (vanhemmassa muodossa tai ilman PDF-yhteensopivuutta tallennettu .ai tai EPS-tiedosto), ei PDF: Tiedosto › Avaa ja Tiedosto › Sijoita lukevat sen EPS-muodossa
|
||||
@msg this system has no screen color sampler tässä järjestelmässä ei ole värin poimintaa näytöltä
|
||||
@msg the image can't be read as a texture kuvaa ei voi lukea tekstuurina
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Korostusväri
|
||||
Amber Meripihka
|
||||
Coral Koralli
|
||||
Violet Violetti
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4931,3 +4931,9 @@ theme Appearance Apparence
|
||||
@msg choose a source folder choisissez un dossier source
|
||||
@msg open a document with data sets ouvrez un document avec des jeux de données
|
||||
@msg the action has no steps l'action n'a aucune étape
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Couleur d’accent
|
||||
Amber Ambre
|
||||
Coral Corail
|
||||
Violet Violet
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4899,3 +4899,9 @@ imageTrace Outline Körvonal
|
||||
@msg choose a source folder válasszon forrásmappát
|
||||
@msg open a document with data sets nyisson meg egy adatkészleteket tartalmazó dokumentumot
|
||||
@msg the action has no steps a műveletnek nincsenek lépései
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Kiemelőszín
|
||||
Amber Borostyán
|
||||
Coral Korall
|
||||
Violet Ibolya
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4508,3 +4508,10 @@ lighting Light Luce
|
||||
@msg choose a source folder scegli una cartella di origine
|
||||
@msg open a document with data sets apri un documento con set di dati
|
||||
@msg the action has no steps l'azione non ha passaggi
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Colore di risalto
|
||||
Amber Ambra
|
||||
Coral Corallo
|
||||
Violet Viola
|
||||
Pink Rosa
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4549,3 +4549,10 @@ lighting Light ライト
|
||||
@msg choose a source folder ソースフォルダーを選択してください
|
||||
@msg open a document with data sets データセットのあるドキュメントを開いてください
|
||||
@msg the action has no steps アクションにステップがありません
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color アクセントカラー
|
||||
Coral コーラル
|
||||
Amber アンバー
|
||||
Violet バイオレット
|
||||
Pink ピンク
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4472,3 +4472,10 @@ lighting Light 광원
|
||||
@msg choose a source folder 소스 폴더를 선택하세요
|
||||
@msg open a document with data sets 데이터 세트가 있는 문서를 여세요
|
||||
@msg the action has no steps 액션에 단계가 없습니다
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color 강조 색상
|
||||
Amber 호박색
|
||||
Coral 코럴
|
||||
Violet 보라
|
||||
Pink 분홍
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4776,3 +4776,10 @@ mark-style Japanese Japońskie
|
||||
@msg choose a source folder wybierz folder źródłowy
|
||||
@msg open a document with data sets otwórz dokument z zestawami danych
|
||||
@msg the action has no steps operacja nie ma kroków
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Kolor akcentu
|
||||
Amber Bursztynowy
|
||||
Coral Koralowy
|
||||
Violet Fioletowy
|
||||
Pink Różowy
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4849,3 +4849,9 @@ mark-style Japanese Japonesas
|
||||
Color Panel Options Opções do Painel Cor
|
||||
@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode` deve ser grayscale, rgb, hsb, cmyk ou websafe
|
||||
@msg the image can't be read as a texture a imagem não pode ser lida como textura
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Cor de destaque
|
||||
Amber Âmbar
|
||||
Coral Coral
|
||||
Violet Violeta
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -4504,3 +4504,10 @@ lighting Light Свет
|
||||
@msg choose a source folder выберите исходную папку
|
||||
@msg open a document with data sets откройте документ с наборами данных
|
||||
@msg the action has no steps в действии нет шагов
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Цвет акцента
|
||||
Amber Янтарный
|
||||
Coral Коралловый
|
||||
Violet Фиолетовый
|
||||
Pink Розовый
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4889,3 +4889,9 @@ theme Appearance Utseende
|
||||
@msg this is a PostScript file (an .ai saved in an older format or without PDF compatibility, or an EPS file), not a PDF: File › Open and File › Place read it as EPS det här är en PostScript-fil (en .ai sparad i ett äldre format eller utan PDF-kompatibilitet, eller en EPS-fil), inte en PDF: Fil › Öppna och Fil › Placera läser den som EPS
|
||||
@msg this system has no screen color sampler det här systemet saknar färghämtning från skärmen
|
||||
@msg the image can't be read as a texture bilden kan inte läsas som en textur
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Accentfärg
|
||||
Amber Bärnsten
|
||||
Coral Korall
|
||||
Violet Violett
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -4488,3 +4488,10 @@ lighting Light Світло
|
||||
@msg choose a source folder виберіть вихідну папку
|
||||
@msg open a document with data sets відкрийте документ із наборами даних
|
||||
@msg the action has no steps у дії немає кроків
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Колір акценту
|
||||
Amber Бурштиновий
|
||||
Coral Кораловий
|
||||
Violet Фіолетовий
|
||||
Pink Рожевий
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 718 and column 40.
|
@@ -4488,3 +4488,10 @@ theme Appearance Giao diện
|
||||
@msg choose a source folder hãy chọn thư mục nguồn
|
||||
@msg open a document with data sets hãy mở một tài liệu có tập dữ liệu
|
||||
@msg the action has no steps hành động không có bước nào
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color Màu nhấn
|
||||
Amber Hổ phách
|
||||
Coral San hô
|
||||
Violet Tím
|
||||
Pink Hồng
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3729 and column 10.
|
@@ -4040,3 +4040,9 @@ lighting Light 光源
|
||||
Destination Folder: 目标文件夹:
|
||||
Overrides 覆盖
|
||||
Uses every data set of the open document. 使用打开文档的每个数据集。
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color 强调色
|
||||
Amber 琥珀色
|
||||
Coral 珊瑚色
|
||||
Violet 紫罗兰色
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3827,3 +3827,10 @@ lighting Light 光源
|
||||
Destination Folder: 目的地資料夾:
|
||||
Overrides 覆寫
|
||||
Uses every data set of the open document. 使用開啟文件的每個資料集。
|
||||
|
||||
# User Interface › Accent Color
|
||||
Accent Color 強調色
|
||||
Amber 琥珀色
|
||||
Coral 珊瑚色
|
||||
Violet 紫羅蘭色
|
||||
Pink 粉紅色
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -644,9 +644,10 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
|
||||
}
|
||||
let mut style = craft_ui::docking::DockStyle::from_ui(ui);
|
||||
style.tab_height = 33.0;
|
||||
style.background = t.panel;
|
||||
style.tab_background = t.tab_strip;
|
||||
style.active_background = t.panel;
|
||||
// The dock is a card (`theme::card`): its panes and tab strip lie on the card's colour.
|
||||
style.background = egui::Color32::TRANSPARENT;
|
||||
style.tab_background = egui::Color32::TRANSPARENT;
|
||||
style.active_background = t.button;
|
||||
style.text = t.text_strong;
|
||||
style.inactive_text = t.text_dim;
|
||||
style.border = egui::Stroke::new(1.0, t.border);
|
||||
@@ -675,13 +676,9 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
|
||||
output
|
||||
};
|
||||
let output = if layout.root.is_some() {
|
||||
let panel = egui::Panel::right("shared-panel-dock")
|
||||
.default_size(310.0)
|
||||
.size_range(220.0..=720.0)
|
||||
.resizable(true)
|
||||
.frame(egui::Frame::NONE.fill(t.panel));
|
||||
let panel = egui::Panel::right("shared-panel-dock").default_size(310.0).size_range(220.0..=720.0).resizable(true).frame(egui::Frame::NONE);
|
||||
let panel = if collapsed { panel.exact_size(38.0).resizable(false) } else { panel };
|
||||
let shown = panel.show(ui, &mut draw);
|
||||
let shown = panel.show(ui, |ui| crate::theme::card(ui, &t, egui::Margin { left: 0, right: 6, top: 6, bottom: 6 }, &mut draw));
|
||||
ui.ctx().data_mut(|data| data.insert_temp(crate::floating::dock_rect_id(), shown.response.rect));
|
||||
shown.inner
|
||||
} else {
|
||||
@@ -767,27 +764,29 @@ fn icon_rail(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
|
||||
let column = egui::Panel::right("shared-panel-icons")
|
||||
.exact_size(38.0)
|
||||
.resizable(false)
|
||||
.frame(egui::Frame::NONE.fill(t.panel))
|
||||
.frame(egui::Frame::NONE)
|
||||
.show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().id_salt("shared-panel-icons-scroll").show(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing.y = 2.0;
|
||||
for &(id, label, icon) in crate::state::ICON_PANEL_GROUPS
|
||||
.iter()
|
||||
.flat_map(|group| group.iter())
|
||||
.filter_map(|id| crate::state::ICON_PANELS.iter().find(|entry| entry.0 == *id))
|
||||
{
|
||||
if app.ui.docking.as_ref().is_some_and(|layout| layout.contains(&id.to_string())) {
|
||||
continue;
|
||||
crate::theme::card(ui, &t, egui::Margin { left: 3, right: 3, top: 6, bottom: 6 }, |ui| {
|
||||
egui::ScrollArea::vertical().id_salt("shared-panel-icons-scroll").show(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing.y = 2.0;
|
||||
for &(id, label, icon) in crate::state::ICON_PANEL_GROUPS
|
||||
.iter()
|
||||
.flat_map(|group| group.iter())
|
||||
.filter_map(|id| crate::state::ICON_PANELS.iter().find(|entry| entry.0 == *id))
|
||||
{
|
||||
if app.ui.docking.as_ref().is_some_and(|layout| layout.contains(&id.to_string())) {
|
||||
continue;
|
||||
}
|
||||
let (_, rect) = ui.allocate_space(egui::vec2(30.0, 30.0));
|
||||
let response = ui.interact(rect, ui.id().with(("panel-icon", id)), egui::Sense::click_and_drag());
|
||||
let response = crate::widgets::paint_icon_button(ui, response, icon, tl!(label), app.ui.open_panel.as_deref() == Some(id));
|
||||
if response.clicked() {
|
||||
app.ui.open_panel = if app.ui.open_panel.as_deref() == Some(id) { None } else { Some(id.into()) };
|
||||
}
|
||||
legacy_tab(app, ui, id, &response);
|
||||
}
|
||||
let (_, rect) = ui.allocate_space(egui::vec2(30.0, 30.0));
|
||||
let response = ui.interact(rect, ui.id().with(("panel-icon", id)), egui::Sense::click_and_drag());
|
||||
let response = crate::widgets::paint_icon_button(ui, response, icon, tl!(label), app.ui.open_panel.as_deref() == Some(id));
|
||||
if response.clicked() {
|
||||
app.ui.open_panel = if app.ui.open_panel.as_deref() == Some(id) { None } else { Some(id.into()) };
|
||||
}
|
||||
legacy_tab(app, ui, id, &response);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
.response
|
||||
.rect;
|
||||
|
||||
@@ -9,7 +9,7 @@ use vectorcraft_engine::Prefs;
|
||||
use vectorcraft_engine::cmd::prefscmds::{GPU_PREFERENCES, PREF_CATEGORIES, PREF_GROUPS, PREF_SPECS, PrefKind};
|
||||
|
||||
use crate::state::Dialog;
|
||||
use crate::theme::{self, Brightness, Tokens};
|
||||
use crate::theme::{self, Accent, Brightness, Tokens};
|
||||
use crate::{VectorcraftApp, widgets};
|
||||
|
||||
/// UI-only fields shown in the dialog (backed by `UiState`, not engine prefs).
|
||||
@@ -81,6 +81,7 @@ pub fn restore(app: &mut VectorcraftApp) {
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
struct Applied {
|
||||
brightness: Brightness,
|
||||
accent: Accent,
|
||||
/// User Interface › Canvas Color, unless it matches the interface brightness.
|
||||
canvas: Option<egui::Color32>,
|
||||
threads: i32,
|
||||
@@ -111,6 +112,7 @@ pub fn apply_runtime(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
}
|
||||
let want = Applied {
|
||||
brightness: app.ui.brightness,
|
||||
accent: Accent::parse(&p.ui_accent).unwrap_or_default(),
|
||||
canvas: canvas_color(&p.canvas_color),
|
||||
threads: p.render_threads,
|
||||
tool_tips: p.show_tool_tips,
|
||||
@@ -120,7 +122,7 @@ pub fn apply_runtime(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
let id = egui::Id::new("dc-applied-prefs");
|
||||
let prev: Option<Applied> = ctx.data(|d| d.get_temp::<Option<Applied>>(id)).flatten();
|
||||
if prev != Some(want) {
|
||||
theme::apply(ctx, want.brightness);
|
||||
theme::apply_with(ctx, want.brightness, want.accent);
|
||||
// General › Show Tool Tips: off, no button or field shows its tool tip (they never come
|
||||
// due), whichever widget asks for one.
|
||||
let delay = if want.tool_tips { egui::style::Interaction::default().tooltip_delay } else { f32::INFINITY };
|
||||
|
||||
+209
-97
@@ -1,4 +1,4 @@
|
||||
//! Design tokens (Illustrator's four UI brightness levels), fonts and egui style.
|
||||
//! Design tokens (four UI brightness levels), fonts and egui style.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -37,6 +37,54 @@ impl Brightness {
|
||||
}
|
||||
}
|
||||
|
||||
/// User Interface › Accent Color: the one colour for what is on, chosen or focused. Coral, the
|
||||
/// default, is the app icon's.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum Accent {
|
||||
#[default]
|
||||
Coral,
|
||||
Blue,
|
||||
Violet,
|
||||
Green,
|
||||
Amber,
|
||||
Pink,
|
||||
}
|
||||
|
||||
impl Accent {
|
||||
pub const ALL: [Accent; 6] = [Accent::Coral, Accent::Blue, Accent::Violet, Accent::Green, Accent::Amber, Accent::Pink];
|
||||
pub fn id(self) -> &'static str {
|
||||
match self {
|
||||
Accent::Coral => "coral",
|
||||
Accent::Blue => "blue",
|
||||
Accent::Violet => "violet",
|
||||
Accent::Green => "green",
|
||||
Accent::Amber => "amber",
|
||||
Accent::Pink => "pink",
|
||||
}
|
||||
}
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
Self::ALL.into_iter().find(|a| a.id().eq_ignore_ascii_case(s))
|
||||
}
|
||||
/// (accent, accent_strong) on dark panels and on light ones; the strong one fills buttons
|
||||
/// with white lettering.
|
||||
fn colors(self, dark: bool) -> (u32, u32) {
|
||||
match (self, dark) {
|
||||
(Accent::Coral, true) => (0xf06a52, 0xe8573f),
|
||||
(Accent::Coral, false) => (0xd9472f, 0xd14a33),
|
||||
(Accent::Blue, true) => (0x5b96ff, 0x2f74f0),
|
||||
(Accent::Blue, false) => (0x2563eb, 0x1f5ad6),
|
||||
(Accent::Violet, true) => (0xa98bff, 0x7c55f0),
|
||||
(Accent::Violet, false) => (0x7146dc, 0x6a3fd4),
|
||||
(Accent::Green, true) => (0x3fcf8e, 0x1f9d63),
|
||||
(Accent::Green, false) => (0x16955a, 0x14874f),
|
||||
(Accent::Amber, true) => (0xf5b544, 0xc47f0e),
|
||||
(Accent::Amber, false) => (0xb06d06, 0xb06d06),
|
||||
(Accent::Pink, true) => (0xf36aa3, 0xdb3f7c),
|
||||
(Accent::Pink, false) => (0xcc3672, 0xc22e69),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// All colours and metrics the UI uses. Never hard-code a colour in a widget.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Tokens {
|
||||
@@ -98,38 +146,41 @@ pub struct Tokens {
|
||||
impl Tokens {
|
||||
pub fn for_brightness(b: Brightness) -> Self {
|
||||
let hex = |s: u32| Color32::from_rgb((s >> 16) as u8, (s >> 8) as u8, s as u8);
|
||||
// The Craft look: cool slate greys, flat faces with soft corners, and one accent (the app
|
||||
// icon's coral) for what is on, chosen or focused. The four brightness levels share the
|
||||
// style and differ only in their greys.
|
||||
let base = Tokens {
|
||||
dark: true,
|
||||
app_bar: hex(0x262626),
|
||||
panel: hex(0x323232),
|
||||
panel_darker: hex(0x282828),
|
||||
input: hex(0x1f1f1f),
|
||||
input_border: hex(0x464646),
|
||||
border: hex(0x1b1b1b),
|
||||
divider: hex(0x3f3f3f),
|
||||
text: hex(0xd8d8d8),
|
||||
text_dim: hex(0xa8a8a8),
|
||||
text_disabled: hex(0x6e6e6e),
|
||||
icon: hex(0xcfcfcf),
|
||||
hover: hex(0x444444),
|
||||
tool_active: hex(0x1d1d1d),
|
||||
accent: hex(0x378ef0),
|
||||
accent_strong: hex(0x1473e6),
|
||||
row_selected: hex(0x484b50),
|
||||
pasteboard: hex(0x1e1e1e),
|
||||
ruler: hex(0x2f2f2f),
|
||||
ruler_tick: hex(0x8a8a8a),
|
||||
app_bar: hex(0x0b0d12),
|
||||
panel: hex(0x181b22),
|
||||
panel_darker: hex(0x13161c),
|
||||
input: hex(0x0f1116),
|
||||
input_border: hex(0x2c323e),
|
||||
border: hex(0x06070a),
|
||||
divider: hex(0x232832),
|
||||
text: hex(0xdfe4ec),
|
||||
text_dim: hex(0x969faf),
|
||||
text_disabled: hex(0x5a6271),
|
||||
icon: hex(0xc9d0dc),
|
||||
hover: hex(0x262b35),
|
||||
tool_active: hex(0x2f3541),
|
||||
accent: hex(0xf06a52),
|
||||
accent_strong: hex(0xe8573f),
|
||||
row_selected: hex(0x3a2a2c),
|
||||
pasteboard: hex(0x101217),
|
||||
ruler: hex(0x13161c),
|
||||
ruler_tick: hex(0x6f788a),
|
||||
smart_guide: hex(0xff3dfc),
|
||||
guide: hex(0x4affff),
|
||||
measure_bg: Color32::from_rgba_unmultiplied(92, 92, 92, 235),
|
||||
button: hex(0x444444),
|
||||
radius: 2,
|
||||
text_strong: hex(0xf5f5f5),
|
||||
tab_strip: hex(0x282828),
|
||||
button_border: hex(0x6a6a6a),
|
||||
measure_bg: Color32::from_rgba_unmultiplied(24, 27, 34, 235),
|
||||
button: hex(0x282d37),
|
||||
radius: 4,
|
||||
text_strong: hex(0xffffff),
|
||||
tab_strip: hex(0x0f1116),
|
||||
button_border: hex(0x4a5363),
|
||||
selection: hex(0x4f80ff),
|
||||
anchor_selected: hex(0x3d82ff),
|
||||
flyout_active: hex(0x54b2f7),
|
||||
flyout_active: hex(0xff8a74),
|
||||
font_size: 13.0,
|
||||
caption_close: hex(0xc42b1c),
|
||||
caption_close_text: Color32::WHITE,
|
||||
@@ -143,92 +194,142 @@ impl Tokens {
|
||||
};
|
||||
match b {
|
||||
Brightness::Dark => base,
|
||||
// Measured from Illustrator 2026 (plan/illustrator/10-observed-ui.md §1).
|
||||
// Charcoal, the default.
|
||||
Brightness::MediumDark => Tokens {
|
||||
app_bar: hex(0x535353),
|
||||
panel: hex(0x535353),
|
||||
panel_darker: hex(0x424242),
|
||||
tab_strip: hex(0x424242),
|
||||
input: hex(0x454545),
|
||||
input_border: hex(0x5f5f5f),
|
||||
border: hex(0x383838),
|
||||
divider: hex(0x4b4b4b),
|
||||
text: hex(0xd1d1d1),
|
||||
text_strong: hex(0xffffff),
|
||||
text_dim: hex(0xb0b0b0),
|
||||
text_disabled: hex(0x7a7a7a),
|
||||
icon: hex(0xc2c2c2),
|
||||
hover: hex(0x606060),
|
||||
tool_active: hex(0x303030),
|
||||
row_selected: hex(0x52677c),
|
||||
pasteboard: hex(0x606060),
|
||||
ruler: hex(0x333333),
|
||||
ruler_tick: hex(0x8a8a8a),
|
||||
button: hex(0x535353),
|
||||
button_border: hex(0x747474),
|
||||
app_bar: hex(0x1b1f27),
|
||||
panel: hex(0x272c36),
|
||||
panel_darker: hex(0x21262f),
|
||||
tab_strip: hex(0x1e222a),
|
||||
input: hex(0x1a1e26),
|
||||
input_border: hex(0x3a414e),
|
||||
border: hex(0x11141a),
|
||||
divider: hex(0x323946),
|
||||
text: hex(0xe4e9f1),
|
||||
text_dim: hex(0x99a2b2),
|
||||
text_disabled: hex(0x626b7a),
|
||||
icon: hex(0xccd3de),
|
||||
hover: hex(0x353c49),
|
||||
tool_active: hex(0x3d4553),
|
||||
row_selected: hex(0x4a3236),
|
||||
pasteboard: hex(0x1e222a),
|
||||
ruler: hex(0x21262f),
|
||||
ruler_tick: hex(0x7c8597),
|
||||
measure_bg: Color32::from_rgba_unmultiplied(39, 44, 54, 235),
|
||||
button: hex(0x39404d),
|
||||
button_border: hex(0x5a6476),
|
||||
..base
|
||||
},
|
||||
Brightness::MediumLight => Tokens {
|
||||
tab_strip: hex(0xa6a6a6),
|
||||
text_strong: hex(0x000000),
|
||||
button_border: hex(0x7a7a7a),
|
||||
dark: false,
|
||||
app_bar: hex(0xa9a9a9),
|
||||
panel: hex(0xb8b8b8),
|
||||
panel_darker: hex(0xaaaaaa),
|
||||
input: hex(0xd6d6d6),
|
||||
input_border: hex(0x8c8c8c),
|
||||
border: hex(0x969696),
|
||||
divider: hex(0x9e9e9e),
|
||||
text: hex(0x1f1f1f),
|
||||
text_dim: hex(0x3c3c3c),
|
||||
text_disabled: hex(0x7c7c7c),
|
||||
icon: hex(0x2a2a2a),
|
||||
hover: hex(0xc8c8c8),
|
||||
tool_active: hex(0x969696),
|
||||
row_selected: hex(0x9fb4d6),
|
||||
pasteboard: hex(0xa0a0a0),
|
||||
ruler: hex(0xc2c2c2),
|
||||
ruler_tick: hex(0x4a4a4a),
|
||||
button: hex(0xcacaca),
|
||||
app_bar: hex(0xc4cad4),
|
||||
panel: hex(0xd3d8e0),
|
||||
panel_darker: hex(0xc9cfd8),
|
||||
tab_strip: hex(0xc4cad4),
|
||||
input: hex(0xe9ecf1),
|
||||
input_border: hex(0x9aa2b0),
|
||||
border: hex(0xa9b0bc),
|
||||
divider: hex(0xbcc3ce),
|
||||
text: hex(0x1b1f27),
|
||||
text_strong: hex(0x000000),
|
||||
text_dim: hex(0x4a5160),
|
||||
text_disabled: hex(0x7d8594),
|
||||
icon: hex(0x272c36),
|
||||
hover: hex(0xc5ccd6),
|
||||
tool_active: hex(0xb6bec9),
|
||||
accent: hex(0xd9472f),
|
||||
accent_strong: hex(0xd14a33),
|
||||
flyout_active: hex(0xc23e28),
|
||||
row_selected: hex(0xe7c4bd),
|
||||
pasteboard: hex(0xb9c0cb),
|
||||
ruler: hex(0xccd2db),
|
||||
ruler_tick: hex(0x4a5160),
|
||||
measure_bg: Color32::from_rgba_unmultiplied(211, 216, 224, 235),
|
||||
button: hex(0xc9d0da),
|
||||
button_border: hex(0x8a93a2),
|
||||
..base
|
||||
},
|
||||
Brightness::Light => Tokens {
|
||||
tab_strip: hex(0xdddddd),
|
||||
text_strong: hex(0x000000),
|
||||
button_border: hex(0x9a9a9a),
|
||||
dark: false,
|
||||
app_bar: hex(0xe4e4e4),
|
||||
panel: hex(0xf0f0f0),
|
||||
panel_darker: hex(0xe2e2e2),
|
||||
app_bar: hex(0xe6e9ee),
|
||||
panel: hex(0xf3f5f8),
|
||||
panel_darker: hex(0xe9ecf1),
|
||||
tab_strip: hex(0xe6e9ee),
|
||||
input: hex(0xffffff),
|
||||
input_border: hex(0xb4b4b4),
|
||||
border: hex(0xcfcfcf),
|
||||
divider: hex(0xd6d6d6),
|
||||
text: hex(0x1f1f1f),
|
||||
text_dim: hex(0x4b4b4b),
|
||||
text_disabled: hex(0x9a9a9a),
|
||||
icon: hex(0x303030),
|
||||
hover: hex(0xdddddd),
|
||||
tool_active: hex(0xcdcdcd),
|
||||
row_selected: hex(0xc8daf7),
|
||||
pasteboard: hex(0xdcdcdc),
|
||||
ruler: hex(0xeaeaea),
|
||||
ruler_tick: hex(0x5a5a5a),
|
||||
button: hex(0xe0e0e0),
|
||||
input_border: hex(0xc3c9d3),
|
||||
border: hex(0xd0d5dd),
|
||||
divider: hex(0xdde1e8),
|
||||
text: hex(0x1b1f27),
|
||||
text_strong: hex(0x000000),
|
||||
text_dim: hex(0x58606e),
|
||||
text_disabled: hex(0x9aa1ad),
|
||||
icon: hex(0x2a303b),
|
||||
hover: hex(0xe3e7ed),
|
||||
tool_active: hex(0xd5dbe3),
|
||||
accent: hex(0xd9472f),
|
||||
accent_strong: hex(0xd14a33),
|
||||
flyout_active: hex(0xc23e28),
|
||||
row_selected: hex(0xf8d9d2),
|
||||
pasteboard: hex(0xdfe3e9),
|
||||
ruler: hex(0xeceff3),
|
||||
ruler_tick: hex(0x58606e),
|
||||
measure_bg: Color32::from_rgba_unmultiplied(243, 245, 248, 235),
|
||||
button: hex(0xe3e7ed),
|
||||
button_border: hex(0xa4abb8),
|
||||
..base
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// These tokens with `accent`: the accent, buttons filled with it, the active item in flyouts and
|
||||
/// selected rows (the panel tinted towards it).
|
||||
pub fn with_accent(self, accent: Accent) -> Self {
|
||||
let hex = |s: u32| Color32::from_rgb((s >> 16) as u8, (s >> 8) as u8, s as u8);
|
||||
let (a, strong) = accent.colors(self.dark);
|
||||
let (a, strong) = (hex(a), hex(strong));
|
||||
let tint = |k: f32| {
|
||||
let mix = |p: u8, q: u8| (f32::from(p) + (f32::from(q) - f32::from(p)) * k).round() as u8;
|
||||
Color32::from_rgb(mix(self.panel.r(), a.r()), mix(self.panel.g(), a.g()), mix(self.panel.b(), a.b()))
|
||||
};
|
||||
Tokens {
|
||||
accent: a,
|
||||
accent_strong: strong,
|
||||
flyout_active: if self.dark { a } else { strong },
|
||||
row_selected: tint(if self.dark { 0.24 } else { 0.2 }),
|
||||
tool_active: tint(if self.dark { 0.3 } else { 0.24 }),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(ctx: &egui::Context) -> Tokens {
|
||||
ctx.data(|d| d.get_temp::<Tokens>(egui::Id::NULL)).unwrap_or_else(|| Tokens::for_brightness(Brightness::MediumDark))
|
||||
ctx.data(|d| d.get_temp::<Tokens>(egui::Id::NULL)).unwrap_or_else(|| Tokens::for_brightness(Brightness::default()))
|
||||
}
|
||||
pub fn cr(&self) -> CornerRadius {
|
||||
CornerRadius::same(self.radius)
|
||||
}
|
||||
}
|
||||
|
||||
/// The toolbar, the panel dock and the icon rail are cards: `body` drawn on the panel colour with
|
||||
/// soft corners, `margin` of the window's frame colour around it.
|
||||
pub fn card<R>(ui: &mut egui::Ui, t: &Tokens, margin: egui::Margin, body: impl FnOnce(&mut egui::Ui) -> R) -> R {
|
||||
egui::Frame::NONE
|
||||
.fill(t.app_bar)
|
||||
.inner_margin(margin)
|
||||
.show(ui, |ui| {
|
||||
egui::Frame::NONE
|
||||
.fill(t.panel)
|
||||
.corner_radius(CARD_RADIUS)
|
||||
.show(ui, |ui| {
|
||||
ui.set_min_size(ui.available_size());
|
||||
body(ui)
|
||||
})
|
||||
.inner
|
||||
})
|
||||
.inner
|
||||
}
|
||||
|
||||
/// The corner radius of [`card`]s and floating windows.
|
||||
pub const CARD_RADIUS: u8 = 8;
|
||||
|
||||
/// Selected text in the text fields `ui` draws from here on is in the strong text colour. egui
|
||||
/// paints it in the selection stroke's colour, the accent, which is hard to read on the selection's
|
||||
/// accent tint (#1465). For fields that draw their own frame: egui's frame takes its focus ring from
|
||||
@@ -254,16 +355,22 @@ pub fn install_fonts(ctx: &egui::Context) {
|
||||
f.font_data.insert(name.to_owned(), Arc::new(FontData::from_static(bytes)));
|
||||
};
|
||||
add(&mut fonts, "SourceSans3", include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf"));
|
||||
// Inter is drawn a little smaller than its size: its letters are wider than Source Sans's, and
|
||||
// the panels are laid out for those.
|
||||
let inter =
|
||||
|bytes: &'static [u8]| Arc::new(FontData::from_static(bytes).tweak(egui::epaint::text::FontTweak { scale: 0.92, ..Default::default() }));
|
||||
fonts.font_data.insert("Inter".to_owned(), inter(include_bytes!("../../../assets/fonts/Inter-Regular.ttf")));
|
||||
fonts.font_data.insert("Inter-SemiBold".to_owned(), inter(include_bytes!("../../../assets/fonts/Inter-SemiBold.ttf")));
|
||||
add(&mut fonts, "SourceSans3-Semibold", include_bytes!("../../../assets/fonts/SourceSans3-Semibold.ttf"));
|
||||
add(&mut fonts, "JetBrainsMono", include_bytes!("../../../assets/fonts/JetBrainsMono-Regular.ttf"));
|
||||
let fallback: Vec<String> = fonts.families.get(&FontFamily::Proportional).cloned().unwrap_or_default();
|
||||
let mut prop = vec!["SourceSans3".to_string()];
|
||||
let mut prop = vec!["Inter".to_string(), "SourceSans3".to_string()];
|
||||
prop.extend(fallback.clone());
|
||||
fonts.families.insert(FontFamily::Proportional, prop);
|
||||
let mut semi = vec!["SourceSans3-Semibold".to_string()];
|
||||
let mut semi = vec!["Inter-SemiBold".to_string(), "SourceSans3-Semibold".to_string()];
|
||||
semi.extend(fallback.clone());
|
||||
fonts.families.insert(FontFamily::Name(FONT_UI_SEMIBOLD.into()), semi);
|
||||
let mut ui = vec!["SourceSans3".to_string()];
|
||||
let mut ui = vec!["Inter".to_string(), "SourceSans3".to_string()];
|
||||
ui.extend(fallback.clone());
|
||||
fonts.families.insert(FontFamily::Name(FONT_UI.into()), ui);
|
||||
let mut mono = vec!["JetBrainsMono".to_string()];
|
||||
@@ -293,7 +400,12 @@ fn add_craft_fonts(fonts: &mut FontDefinitions) {
|
||||
|
||||
/// Apply tokens to egui's global style.
|
||||
pub fn apply(ctx: &egui::Context, b: Brightness) {
|
||||
let t = Tokens::for_brightness(b);
|
||||
apply_with(ctx, b, Accent::default());
|
||||
}
|
||||
|
||||
/// [`apply`] with an accent colour.
|
||||
pub fn apply_with(ctx: &egui::Context, b: Brightness, accent: Accent) {
|
||||
let t = Tokens::for_brightness(b).with_accent(accent);
|
||||
ctx.data_mut(|d| d.insert_temp(egui::Id::NULL, t));
|
||||
let mut v = if t.dark { Visuals::dark() } else { Visuals::light() };
|
||||
v.panel_fill = t.panel;
|
||||
@@ -301,8 +413,8 @@ pub fn apply(ctx: &egui::Context, b: Brightness) {
|
||||
v.extreme_bg_color = t.input;
|
||||
v.faint_bg_color = t.panel_darker;
|
||||
v.window_stroke = Stroke::new(1.0, t.border);
|
||||
v.window_corner_radius = CornerRadius::same(6);
|
||||
v.menu_corner_radius = CornerRadius::same(6);
|
||||
v.window_corner_radius = CornerRadius::same(10);
|
||||
v.menu_corner_radius = CornerRadius::same(8);
|
||||
v.selection.bg_fill = t.accent.gamma_multiply(0.55);
|
||||
v.selection.stroke = Stroke::new(1.0, t.accent);
|
||||
v.hyperlink_color = t.accent;
|
||||
|
||||
@@ -104,11 +104,12 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
// Where the panel docks: along the window's left edge, under the bars.
|
||||
let edge = ui.available_rect_before_wrap();
|
||||
ui.ctx().data_mut(|d| d.insert_temp(crate::floating::tools_zone_id(), egui::Rect::from_min_size(edge.min, vec2(WIDTH, edge.height()))));
|
||||
let frame =
|
||||
egui::Frame::NONE.fill(t.panel).inner_margin(egui::Margin { left: 0, right: 0, top: 0, bottom: 4 }).stroke(Stroke::new(1.5, t.border));
|
||||
match app.ui.toolbar_pos {
|
||||
None => {
|
||||
egui::Panel::left("toolbar").resizable(false).exact_size(w).frame(frame).show(ui, |ui| body(app, ui, cols, None));
|
||||
let margin = egui::Margin { left: 6, right: 0, top: 6, bottom: 6 };
|
||||
egui::Panel::left("toolbar").resizable(false).exact_size(w + 6.0).frame(egui::Frame::NONE).show(ui, |ui| {
|
||||
crate::theme::card(ui, &t, margin, |ui| body(app, ui, cols, None));
|
||||
});
|
||||
}
|
||||
Some(pos) => {
|
||||
// Floating, kept inside the window, its tools scrolling when they don't fit under it.
|
||||
@@ -221,11 +222,11 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, cols: usize, floating: Option<egu
|
||||
let Some(shown) = tool_info(&shown_id).or_else(|| tool_info(slot[0])) else { continue };
|
||||
let is_active = slot.contains(&active);
|
||||
let (rect, resp) = ui.allocate_exact_size(vec2(36.0, PITCH - 1.0), Sense::click_and_drag());
|
||||
let well = egui::Rect::from_center_size(rect.center(), vec2(35.5, 27.5));
|
||||
let well = egui::Rect::from_center_size(rect.center(), vec2(30.0, 27.5));
|
||||
if is_active {
|
||||
ui.painter().rect_filled(well, CornerRadius::same(1), t.tool_active);
|
||||
ui.painter().rect_filled(well, CornerRadius::same(6), t.tool_active);
|
||||
} else if resp.hovered() {
|
||||
ui.painter().rect_filled(well, CornerRadius::same(1), t.hover);
|
||||
ui.painter().rect_filled(well, CornerRadius::same(6), t.hover);
|
||||
}
|
||||
let ir = egui::Rect::from_center_size(rect.center(), vec2(18.0, 18.0));
|
||||
icons::paint(ui, icons::tool_icon(shown.icon), ir, if is_active { t.text_strong } else { t.icon });
|
||||
@@ -693,9 +694,9 @@ fn tool_strip(ui: &mut Ui, t: &Tokens, active: &str, tools: &[String]) -> Option
|
||||
let (r, resp) = ui.allocate_exact_size(STRIP_BUTTON, Sense::click());
|
||||
let well = r.shrink(1.0);
|
||||
if active == tool.id {
|
||||
ui.painter().rect_filled(well, CornerRadius::same(1), t.tool_active);
|
||||
ui.painter().rect_filled(well, CornerRadius::same(6), t.tool_active);
|
||||
} else if resp.hovered() {
|
||||
ui.painter().rect_filled(well, CornerRadius::same(1), t.hover);
|
||||
ui.painter().rect_filled(well, CornerRadius::same(6), t.hover);
|
||||
}
|
||||
let ir = egui::Rect::from_center_size(r.center(), vec2(18.0, 18.0));
|
||||
icons::paint(ui, icons::tool_icon(tool.icon), ir, if active == tool.id { t.text_strong } else { t.icon });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Shared widgets in Illustrator's panel style. Colours come from theme tokens.
|
||||
//! Shared widgets. Colours come from theme tokens.
|
||||
|
||||
use egui::{Color32, CornerRadius, Pos2, Rect, Response, Sense, Stroke, StrokeKind, Ui, Vec2, pos2, vec2};
|
||||
use vectorcraft_color::{BlendMode, Color, Paint};
|
||||
@@ -25,9 +25,9 @@ pub fn paint_icon_button(ui: &mut Ui, resp: Response, icon: &str, tip: &str, sel
|
||||
} else {
|
||||
Color32::TRANSPARENT
|
||||
};
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(3), bg);
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(5), bg);
|
||||
let pad = (size * 0.2).round();
|
||||
icons::paint(ui, icon, rect.shrink(pad), if selected { t.text } else { t.icon });
|
||||
icons::paint(ui, icon, rect.shrink(pad), if selected { t.text_strong } else { t.icon });
|
||||
if !tip.is_empty() { resp.on_hover_text(tl!(tip)) } else { resp }
|
||||
}
|
||||
|
||||
@@ -36,13 +36,8 @@ pub fn flat_button(ui: &mut Ui, text: &str, width: f32) -> Response {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let (rect, resp) = ui.allocate_exact_size(vec2(width, 24.0), Sense::click());
|
||||
let rect = rect.shrink2(vec2(0.0, 0.5));
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(5), t.button);
|
||||
flat_fill(ui, rect, &resp);
|
||||
ui.painter().rect_stroke(
|
||||
rect,
|
||||
CornerRadius::same(2),
|
||||
Stroke::new(1.0, if resp.hovered() { t.text } else { t.button_border }),
|
||||
StrokeKind::Inside,
|
||||
);
|
||||
ui.painter().with_clip_rect(rect).text(rect.center(), egui::Align2::CENTER_CENTER, tl!(text), egui::FontId::proportional(12.5), t.text_strong);
|
||||
resp
|
||||
}
|
||||
@@ -61,11 +56,10 @@ pub fn split_button(ui: &mut Ui, text: &str, width: f32) -> (Response, Response)
|
||||
let arrow_rect = Rect::from_min_max(pos2(split, rect.top()), rect.max);
|
||||
let main = ui.interact(main_rect, ui.id().with((text, "split")), Sense::click());
|
||||
let arrow = ui.interact(arrow_rect, ui.id().with((text, "split-arrow")), Sense::click());
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(5), t.button);
|
||||
flat_fill(ui, main_rect, &main);
|
||||
flat_fill(ui, arrow_rect, &arrow);
|
||||
let border = if main.hovered() || arrow.hovered() { t.text } else { t.button_border };
|
||||
ui.painter().rect_stroke(rect, CornerRadius::same(2), Stroke::new(1.0, border), StrokeKind::Inside);
|
||||
ui.painter().vline(split, rect.y_range().shrink(4.0), Stroke::new(1.0, t.button_border));
|
||||
ui.painter().vline(split, rect.y_range().shrink(5.0), Stroke::new(1.0, t.panel));
|
||||
ui.painter().with_clip_rect(main_rect).text(
|
||||
main_rect.center(),
|
||||
egui::Align2::CENTER_CENTER,
|
||||
@@ -81,13 +75,13 @@ pub fn split_button(ui: &mut Ui, text: &str, width: f32) -> (Response, Response)
|
||||
fn flat_fill(ui: &Ui, rect: Rect, resp: &Response) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
if resp.is_pointer_button_down_on() {
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(2), t.tool_active);
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(5), t.tool_active);
|
||||
} else if resp.hovered() {
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(2), t.hover);
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(5), t.hover);
|
||||
}
|
||||
}
|
||||
|
||||
/// Blue call-to-action pill (dialog OK/Create).
|
||||
/// The accent call-to-action button (dialog OK/Create).
|
||||
pub fn primary_button(ui: &mut Ui, text: &str) -> Response {
|
||||
primary_button_enabled(ui, text, true)
|
||||
}
|
||||
@@ -104,24 +98,21 @@ pub fn primary_button_enabled(ui: &mut Ui, text: &str, enabled: bool) -> Respons
|
||||
(true, true) => t.accent,
|
||||
(true, false) => t.accent_strong,
|
||||
};
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(14), bg);
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(6), bg);
|
||||
if !enabled {
|
||||
ui.painter().rect_stroke(rect, CornerRadius::same(14), egui::Stroke::new(1.0, t.button_border), egui::StrokeKind::Inside);
|
||||
ui.painter().rect_stroke(rect, CornerRadius::same(6), egui::Stroke::new(1.0, t.button_border), egui::StrokeKind::Inside);
|
||||
}
|
||||
ui.painter().galley(rect.center() - galley.size() / 2.0, galley, fg);
|
||||
resp
|
||||
}
|
||||
|
||||
/// Outlined secondary pill (dialog Cancel).
|
||||
/// The quiet secondary button (dialog Cancel).
|
||||
pub fn secondary_button(ui: &mut Ui, text: &str) -> Response {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let galley = ui.painter().layout_no_wrap(tl!(text).to_string(), theme::semibold(12.5), t.text);
|
||||
let w = galley.size().x + 32.0;
|
||||
let (rect, resp) = ui.allocate_exact_size(vec2(w.max(72.0), 28.0), Sense::click());
|
||||
if resp.hovered() {
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(14), t.hover);
|
||||
}
|
||||
ui.painter().rect_stroke(rect, CornerRadius::same(14), Stroke::new(1.5, t.text_dim), StrokeKind::Inside);
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(6), if resp.hovered() { t.hover } else { t.button });
|
||||
ui.painter().galley(rect.center() - galley.size() / 2.0, galley, t.text);
|
||||
resp
|
||||
}
|
||||
@@ -130,7 +121,7 @@ pub fn secondary_button(ui: &mut Ui, text: &str) -> Response {
|
||||
pub fn section_header(ui: &mut Ui, text: &str) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
ui.add_space(2.0);
|
||||
ui.label(egui::RichText::new(tl!(text)).size(13.0).color(t.text));
|
||||
ui.label(egui::RichText::new(tl!(text)).size(12.5).color(t.text_strong));
|
||||
ui.add_space(2.0);
|
||||
}
|
||||
|
||||
@@ -421,7 +412,7 @@ fn recessed_text(ui: &mut Ui, id: egui::Id, shown: &str, width: f32, rows: usize
|
||||
.allocate_ui_with_layout(vec2(width, height), layout, |ui| {
|
||||
ui.set_min_size(vec2(width, height));
|
||||
let rect = ui.max_rect();
|
||||
ui.painter().rect(rect, 2.0, t.input, Stroke::new(1.0, if editing { t.accent } else { t.input_border }), StrokeKind::Inside);
|
||||
ui.painter().rect(rect, 5.0, t.input, Stroke::new(1.0, if editing { t.accent } else { t.input_border }), StrokeKind::Inside);
|
||||
let margin = egui::Margin { left: 7, right: 7, top: if rows > 1 { 4 } else { 0 }, bottom: 0 };
|
||||
let resp = egui::Frame::NONE
|
||||
.inner_margin(margin)
|
||||
@@ -1490,9 +1481,11 @@ pub fn hex_field(ui: &mut Ui, id: impl std::hash::Hash + std::fmt::Debug, hex: &
|
||||
let resp = egui::Frame::NONE
|
||||
.fill(t.input)
|
||||
.stroke(Stroke::new(1.0, if editing { t.accent } else { t.input_border }))
|
||||
.corner_radius(2)
|
||||
.corner_radius(5)
|
||||
.inner_margin(egui::Margin::symmetric(6, 4))
|
||||
.show(ui, |ui| {
|
||||
// As tall as the other fields (26 points with the margin), whatever the font's line height.
|
||||
ui.set_height(18.0);
|
||||
ui.add(
|
||||
egui::TextEdit::singleline(&mut buf)
|
||||
.id(id)
|
||||
|
||||
Reference in New Issue
Block a user