mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-09 02:29:50 -10:00
Restyle the launcher in the new design language
A top bar (the mark, Horizon, the pages as text) replaces the sidebar; a mono footer carries the launcher's version, its update and the community links. Every page is a display title, a short muted line, then an index list: Play's resources and folders, the versions with their names large and their size and state in mono, the patch notes, the settings. Buttons are pills, the main action is the one violet thing on the screen, selection is an ink pill, the console, notices and the crash report sit on soft plates without borders. The Play page ends with the faint wordmark, half set below the window's edge. The sunset photograph and its veils are gone (and the `image` dependency with them): the window is the page colour, light or dark with the system. The opening draws the wordmark letter by letter, then the launcher rises into place. HORIZON_LAUNCHER_APPEARANCE=light|dark overrides the system's appearance, for screenshots and tests. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
a248e9242a
commit
86149f7da6
7 files changed
+855
-869
No files matched your search
Generated
-1
@@ -2575,7 +2575,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"horizon-prepare",
|
||||
"horizon-theme",
|
||||
"image",
|
||||
"keyring",
|
||||
"oo7",
|
||||
"pulldown-cmark",
|
||||
|
||||
@@ -18,7 +18,6 @@ tar = { version = "0.4", default-features = false }
|
||||
zip = { version = "2", default-features = false, features = ["deflate"] }
|
||||
dirs = "5"
|
||||
self-replace = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["jpeg"] }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
horizon-theme = { path = "../crates/horizon-theme" }
|
||||
horizon-prepare = { path = "../crates/horizon-prepare" }
|
||||
|
||||
+3
-4
@@ -25,7 +25,7 @@ The opening, then Play ([clip](docs/opening.webm)):
|
||||
preparation reuses checksum-verified game jars and libraries from the current
|
||||
or another installed Horizon version, so the original Minecraft install is
|
||||
only required when no valid imported copy exists.
|
||||
- **Patch notes**: each release's notes, newest first, with a link to GitHub Sponsors. Quiet "Join Discord" and "Website" links sit in the sidebar footer.
|
||||
- **Patch notes**: each release's notes, newest first, with a link to GitHub Sponsors. Quiet "Discord" and "Website" links sit in the footer.
|
||||
|
||||
Data lives in the platform's data directory (`~/.local/share/horizon-launcher` on Linux,
|
||||
`~/Library/Application Support/horizon-launcher` on macOS, `%APPDATA%\horizon-launcher` on
|
||||
@@ -49,7 +49,7 @@ is open, and shows a small "available, Install" line on Play.
|
||||
|
||||
It also updates itself from GitHub releases tagged `launcher-vX.Y.Z` (Horizon's own are
|
||||
`vYYYY.MM.DD`; neither is ever taken for the other). It downloads the new build, replaces its
|
||||
own binary, and offers "Restart to update" in the sidebar. Development builds and
|
||||
own binary, and offers "Restart" in the footer. Development builds and
|
||||
`HORIZON_LAUNCHER_NO_SELF_UPDATE=1` never replace themselves.
|
||||
|
||||
Launcher releases are automatic (docs/release-flow.md in the repository): when main changes the
|
||||
@@ -139,10 +139,9 @@ client starts (`--help`) and that the binaries import only system libraries (Win
|
||||
cargo test --lib # tag separation, version order, update selection, notes as text,
|
||||
# the auto-install default, the links fallback, the menu style file
|
||||
|
||||
Geist, Geist Mono and Instrument Serif (SIL Open Font License, `crates/horizon-theme/fonts/OFL-*.txt`, shared with the game's menus) are
|
||||
Geist and Geist Mono (SIL Open Font License, `crates/horizon-theme/fonts/OFL-*.txt`, shared with the game's menus) are
|
||||
bundled so the type is the same on every platform.
|
||||
|
||||
Background image (`assets/background.jpg`): backgrounds.supply (free preview), licence to confirm before release.
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 99 KiB |
+801
-811
File diff suppressed because it is too large.
Load diff
+19
-21
@@ -144,7 +144,7 @@ pub fn render(source: &str, t: &Theme) -> AnyElement {
|
||||
let style = match span {
|
||||
Span::Strong => HighlightStyle {
|
||||
font_weight: Some(FontWeight::SEMIBOLD),
|
||||
color: Some(t.text),
|
||||
color: Some(t.ink),
|
||||
..Default::default()
|
||||
},
|
||||
Span::Emphasis => HighlightStyle {
|
||||
@@ -152,12 +152,12 @@ pub fn render(source: &str, t: &Theme) -> AnyElement {
|
||||
..Default::default()
|
||||
},
|
||||
Span::Code => HighlightStyle {
|
||||
background_color: Some(t.raised),
|
||||
color: Some(t.text),
|
||||
background_color: Some(t.plate_soft),
|
||||
color: Some(t.ink),
|
||||
..Default::default()
|
||||
},
|
||||
Span::Link => HighlightStyle {
|
||||
color: Some(t.text),
|
||||
color: Some(t.ink),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
@@ -169,16 +169,16 @@ pub fn render(source: &str, t: &Theme) -> AnyElement {
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap(px(8.))
|
||||
.text_size(px(13.5))
|
||||
.line_height(px(21.))
|
||||
.text_color(t.muted);
|
||||
.text_size(px(crate::theme::UI.px))
|
||||
.line_height(px(22.))
|
||||
.text_color(t.ink_soft);
|
||||
for block in blocks {
|
||||
column = column.child(match block {
|
||||
Block::Heading(level, text, spans) => div()
|
||||
.pt(px(if level <= 2 { 12. } else { 6. }))
|
||||
.text_size(px(if level <= 2 { 14.5 } else { 13.5 }))
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
.text_color(t.text)
|
||||
.text_size(px(if level <= 2 { 16. } else { 14. }))
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.text_color(t.ink)
|
||||
.child(styled(text, spans))
|
||||
.into_any_element(),
|
||||
Block::Paragraph(text, spans) => div().child(styled(text, spans)).into_any_element(),
|
||||
@@ -187,7 +187,7 @@ pub fn render(source: &str, t: &Theme) -> AnyElement {
|
||||
.gap(px(8.))
|
||||
.pl(px(depth as f32 * 16.))
|
||||
.child(
|
||||
div().flex_none().w(px(16.)).text_color(t.faint).child(
|
||||
div().flex_none().w(px(16.)).text_color(t.muted).child(
|
||||
number
|
||||
.map(|n| format!("{n}."))
|
||||
.unwrap_or_else(|| "–".into()),
|
||||
@@ -196,32 +196,30 @@ pub fn render(source: &str, t: &Theme) -> AnyElement {
|
||||
.child(div().flex_1().min_w_0().child(styled(text, spans)))
|
||||
.into_any_element(),
|
||||
Block::Code(code) => div()
|
||||
.p(px(12.))
|
||||
.rounded(px(6.))
|
||||
.border_1()
|
||||
.border_color(t.border)
|
||||
.bg(t.raised)
|
||||
.p(px(16.))
|
||||
.rounded(px(crate::theme::RADIUS_PLATE))
|
||||
.bg(t.plate_soft)
|
||||
.font_family(crate::theme::MONO)
|
||||
.text_size(px(12.))
|
||||
.line_height(px(18.))
|
||||
.text_color(t.text)
|
||||
.text_color(t.ink)
|
||||
.child(code.trim_end().to_string())
|
||||
.into_any_element(),
|
||||
Block::Rule => div().h(px(1.)).bg(t.border).my(px(8.)).into_any_element(),
|
||||
Block::Rule => div().h(px(1.)).bg(t.rule).my(px(8.)).into_any_element(),
|
||||
Block::Table(rows) => {
|
||||
let mut grid = div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.my(px(4.))
|
||||
.border_t_1()
|
||||
.border_color(t.border);
|
||||
.border_color(t.rule);
|
||||
for (i, row) in rows.into_iter().enumerate() {
|
||||
let mut line = div()
|
||||
.flex()
|
||||
.gap(px(16.))
|
||||
.py(px(8.))
|
||||
.border_b_1()
|
||||
.border_color(t.border);
|
||||
.border_color(t.rule);
|
||||
let columns = row.len();
|
||||
for (j, (text, spans)) in row.into_iter().enumerate() {
|
||||
// Two-column tables keep a narrow first column; wider ones share the width.
|
||||
@@ -234,7 +232,7 @@ pub fn render(source: &str, t: &Theme) -> AnyElement {
|
||||
};
|
||||
line = line.child(
|
||||
cell.when(i == 0, |d| {
|
||||
d.text_color(t.text)
|
||||
d.text_color(t.ink)
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.text_size(px(12.5))
|
||||
})
|
||||
|
||||
+32
-31
@@ -5,43 +5,36 @@ use gpui::{rgb, Hsla, WindowAppearance};
|
||||
use horizon_theme as tokens;
|
||||
use std::time::Duration;
|
||||
|
||||
pub use tokens::{Step, BODY, CAPTION, DISPLAY, HEADING, LABEL, MICRO, TITLE, UI, WORDMARK};
|
||||
pub const FONT: &str = tokens::FONT;
|
||||
/// Display type is Geist too, at medium weight.
|
||||
pub const DISPLAY: &str = tokens::FONT;
|
||||
pub const MONO: &str = tokens::MONO;
|
||||
|
||||
pub const SMALL: f32 = tokens::CAPTION.px;
|
||||
pub const BODY: f32 = tokens::LABEL.px;
|
||||
pub const LARGE: f32 = tokens::BODY.px;
|
||||
pub const TITLE: f32 = tokens::TITLE.px;
|
||||
pub const HERO: f32 = tokens::DISPLAY.px;
|
||||
|
||||
/// Shapes and controls, shared with the game's menus.
|
||||
pub use tokens::{CONTROL, DISABLED, PRESSED, RADIUS, SEGMENT};
|
||||
pub const PRESSED_SOFT: f32 = tokens::PRESSED;
|
||||
pub const PRIMARY_HOVER: f32 = 0.10;
|
||||
pub const RADIUS_SEGMENT: f32 = tokens::RADIUS;
|
||||
pub const RADIUS_GROUP: f32 = tokens::RADIUS;
|
||||
pub const ROW_LABEL: f32 = 150.;
|
||||
pub const SIDEBAR: f32 = 200.;
|
||||
/// Shapes and controls, shared with the game's menus and the website.
|
||||
pub use tokens::{
|
||||
CONTROL, CONTROL_LARGE, DISABLED, FIELD, GUTTER, INDEX, PLATE_BLEED, PRESSED, RADIUS,
|
||||
RADIUS_PILL, RADIUS_PLATE, RISE, ROW, SEGMENT,
|
||||
};
|
||||
|
||||
/// Motion: short and eased out, never bouncy.
|
||||
pub const QUICK: Duration = tokens::QUICK.duration();
|
||||
pub const MOVE: Duration = tokens::MOVE.duration();
|
||||
pub const ENTER: Duration = tokens::ENTER.duration();
|
||||
pub const STAGGER: Duration = tokens::STAGGER;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Theme {
|
||||
pub bg: Hsla,
|
||||
pub sidebar: Hsla,
|
||||
pub raised: Hsla,
|
||||
pub hover: Hsla,
|
||||
pub border: Hsla,
|
||||
pub text: Hsla,
|
||||
pub page: Hsla,
|
||||
pub plate_soft: Hsla,
|
||||
pub plate: Hsla,
|
||||
pub surface: Hsla,
|
||||
pub rule: Hsla,
|
||||
pub ink: Hsla,
|
||||
pub ink_soft: Hsla,
|
||||
pub muted: Hsla,
|
||||
pub faint: Hsla,
|
||||
pub accent: Hsla,
|
||||
pub on_accent: Hsla,
|
||||
pub danger: Hsla,
|
||||
pub success: Hsla,
|
||||
pub dark: bool,
|
||||
}
|
||||
|
||||
@@ -50,7 +43,14 @@ fn c(hex: u32) -> Hsla {
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
/// The system's appearance; `HORIZON_LAUNCHER_APPEARANCE=light` or `dark` overrides it (for
|
||||
/// screenshots and tests).
|
||||
pub fn for_appearance(appearance: WindowAppearance) -> Self {
|
||||
match std::env::var("HORIZON_LAUNCHER_APPEARANCE").as_deref() {
|
||||
Ok("light") => return Self::light(),
|
||||
Ok("dark") => return Self::dark(),
|
||||
_ => {}
|
||||
}
|
||||
match appearance {
|
||||
WindowAppearance::Light | WindowAppearance::VibrantLight => Self::light(),
|
||||
_ => Self::dark(),
|
||||
@@ -67,17 +67,18 @@ impl Theme {
|
||||
|
||||
fn from(p: tokens::Palette) -> Self {
|
||||
Self {
|
||||
bg: c(p.page),
|
||||
sidebar: c(p.plate_soft),
|
||||
raised: c(p.plate_soft),
|
||||
hover: c(p.plate),
|
||||
border: c(p.rule),
|
||||
text: c(p.ink),
|
||||
muted: c(p.ink_soft),
|
||||
faint: c(p.muted),
|
||||
page: c(p.page),
|
||||
plate_soft: c(p.plate_soft),
|
||||
plate: c(p.plate),
|
||||
surface: c(p.surface),
|
||||
rule: c(p.rule),
|
||||
ink: c(p.ink),
|
||||
ink_soft: c(p.ink_soft),
|
||||
muted: c(p.muted),
|
||||
accent: c(p.accent),
|
||||
on_accent: c(p.on_accent),
|
||||
danger: c(p.danger),
|
||||
success: c(p.success),
|
||||
dark: p.dark,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user