Files
Ely-GPUI-Components/src/primitives/icon.rs
T
ZacharyZhang-NYandClaude Opus 5.5 d0175edb7b T26c Media: video
Scrubber shows what has loaded, what has played and its chapters, names
the time under the pointer, seeks by press and gpui's drag, and steps by
the slider's keys; over_media lights it for a dark layer. VideoPlayer
shows the host's frame in its shape with a caption and a control bar over
it: the scrubber, play, the time, speed, captions, picture in picture and
full screen, each only with its handler; the bar rests after a still
pointer while it plays, on one wake from the executor's timer. Keys:
Space and K, J and L, the arrows, C and F. VideoThumbnailStrip lays frames
along the length under a playhead with a trim whose handles drag and whose
ends I and O set. SubtitleEditor edits cues in place: times as mm:ss.mmm,
kept in order, a time that does not read kept out with the reason shown.

InlineEdit is a Tab stop with the ring; it hands focus back after Enter or
Escape and keeps its text on Enter's release. overlays::media_button is
shared by the lightbox and the player's bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 17:07:26 -04:00

399 lines
11 KiB
Rust

use gpui::{
App, Hsla, IntoElement, Radians, RenderOnce, SharedString, Transformation, Window, prelude::*,
size, svg,
};
use crate::theme::{ActiveTheme, IconSize};
macro_rules! icons {
($($variant:ident => $file:literal,)*) => {
/// Lucide icons bundled with Ely.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum IconName {
$($variant,)*
}
impl IconName {
pub const ALL: &[IconName] = &[$(IconName::$variant,)*];
pub fn path(self) -> &'static str {
match self {
$(IconName::$variant => concat!("icons/", $file, ".svg"),)*
}
}
/// Lucide name, such as `chevron-down`.
pub fn name(self) -> &'static str {
match self {
$(IconName::$variant => $file,)*
}
}
}
};
}
icons! {
AArrowDown => "a-arrow-down",
AArrowUp => "a-arrow-up",
Accessibility => "accessibility",
Activity => "activity",
AlarmClock => "alarm-clock",
AlignCenter => "align-center",
AlignJustify => "align-justify",
AlignLeft => "align-left",
AlignRight => "align-right",
AppWindow => "app-window",
Archive => "archive",
ArrowDownLeft => "arrow-down-left",
ArrowDownToDot => "arrow-down-to-dot",
ArrowDownToLine => "arrow-down-to-line",
ArrowDownWideNarrow => "arrow-down-wide-narrow",
ArrowDown => "arrow-down",
ArrowLeft => "arrow-left",
ArrowRight => "arrow-right",
ArrowUpFromDot => "arrow-up-from-dot",
ArrowUpNarrowWide => "arrow-up-narrow-wide",
ArrowUpRight => "arrow-up-right",
ArrowUp => "arrow-up",
AtSign => "at-sign",
AudioLines => "audio-lines",
Ban => "ban",
Battery => "battery",
Bell => "bell",
Bold => "bold",
BookOpen => "book-open",
Bookmark => "bookmark",
Bot => "bot",
Box => "box",
Braces => "braces",
Brush => "brush",
Bug => "bug",
CalendarDays => "calendar-days",
CalendarRange => "calendar-range",
Calendar => "calendar",
Camera => "camera",
Captions => "captions",
CaptionsOff => "captions-off",
CaseSensitive => "case-sensitive",
ChartArea => "chart-area",
ChartBar => "chart-bar",
ChartCandlestick => "chart-candlestick",
ChartColumn => "chart-column",
ChartLine => "chart-line",
ChartPie => "chart-pie",
CheckCheck => "check-check",
Check => "check",
ChevronDown => "chevron-down",
ChevronLeft => "chevron-left",
ChevronRight => "chevron-right",
ChevronUp => "chevron-up",
ChevronsLeft => "chevrons-left",
ChevronsLeftRight => "chevrons-left-right",
ChevronsRight => "chevrons-right",
ChevronsUpDown => "chevrons-up-down",
CircleAlert => "circle-alert",
CircleCheck => "circle-check",
CircleDot => "circle-dot",
CircleHelp => "circle-help",
CirclePause => "circle-pause",
CircleStop => "circle-stop",
CircleX => "circle-x",
Circle => "circle",
ClipboardCheck => "clipboard-check",
Clipboard => "clipboard",
Clock => "clock",
CloudAlert => "cloud-alert",
CloudCheck => "cloud-check",
CloudOff => "cloud-off",
Cloud => "cloud",
CloudUpload => "cloud-upload",
Code => "code",
Columns2 => "columns-2",
Command => "command",
Compass => "compass",
Contrast => "contrast",
Copy => "copy",
CornerDownRight => "corner-down-right",
Cpu => "cpu",
CreditCard => "credit-card",
Crop => "crop",
Crosshair => "crosshair",
Database => "database",
Dices => "dices",
DollarSign => "dollar-sign",
Dot => "dot",
Download => "download",
EllipsisVertical => "ellipsis-vertical",
Ellipsis => "ellipsis",
Eraser => "eraser",
ExternalLink => "external-link",
EyeOff => "eye-off",
Eye => "eye",
FilePlus => "file-plus",
FileText => "file-text",
File => "file",
Filter => "filter",
Fingerprint => "fingerprint",
Flag => "flag",
FolderOpen => "folder-open",
FolderPlus => "folder-plus",
Folder => "folder",
Forward => "forward",
Frame => "frame",
Fullscreen => "fullscreen",
Gauge => "gauge",
GitBranch => "git-branch",
GitCommitHorizontal => "git-commit-horizontal",
GitMerge => "git-merge",
GitPullRequest => "git-pull-request",
GitPullRequestClosed => "git-pull-request-closed",
GitPullRequestDraft => "git-pull-request-draft",
Globe => "globe",
GripHorizontal => "grip-horizontal",
GripVertical => "grip-vertical",
Hand => "hand",
HardDrive => "hard-drive",
Hash => "hash",
Heading => "heading",
Heading1 => "heading-1",
Heading2 => "heading-2",
Heading3 => "heading-3",
Heart => "heart",
History => "history",
Hourglass => "hourglass",
House => "house",
Image => "image",
ImageOff => "image-off",
Inbox => "inbox",
Info => "info",
Italic => "italic",
Kanban => "kanban",
Key => "key",
Keyboard => "keyboard",
Languages => "languages",
Layers => "layers",
LayoutDashboard => "layout-dashboard",
LayoutGrid => "layout-grid",
Library => "library",
Lightbulb => "lightbulb",
Link => "link",
ListChecks => "list-checks",
ListOrdered => "list-ordered",
ListTodo => "list-todo",
List => "list",
LoaderCircle => "loader-circle",
LockOpen => "lock-open",
Lock => "lock",
LogIn => "log-in",
LogOut => "log-out",
Mail => "mail",
MapPin => "map-pin",
Map => "map",
Maximize2 => "maximize-2",
MemoryStick => "memory-stick",
Menu => "menu",
Merge => "merge",
MessageSquare => "message-square",
MessageSquareDiff => "message-square-diff",
MicOff => "mic-off",
Mic => "mic",
Minimize2 => "minimize-2",
Minus => "minus",
Monitor => "monitor",
Moon => "moon",
MousePointer2 => "mouse-pointer-2",
Move => "move",
Network => "network",
NotebookPen => "notebook-pen",
Package => "package",
Palette => "palette",
PanelBottom => "panel-bottom",
PanelLeft => "panel-left",
PanelRight => "panel-right",
Paperclip => "paperclip",
Pause => "pause",
PenLine => "pen-line",
PenTool => "pen-tool",
Pencil => "pencil",
PhoneOff => "phone-off",
Phone => "phone",
PictureInPicture2 => "picture-in-picture-2",
Pipette => "pipette",
PinOff => "pin-off",
Pin => "pin",
Play => "play",
Plus => "plus",
Power => "power",
Printer => "printer",
Puzzle => "puzzle",
Quote => "quote",
Redo2 => "redo-2",
RedoDot => "redo-dot",
RefreshCw => "refresh-cw",
Regex => "regex",
Reply => "reply",
Rocket => "rocket",
RotateCcw => "rotate-ccw",
RotateCw => "rotate-cw",
Rows2 => "rows-2",
Save => "save",
Scissors => "scissors",
Search => "search",
SearchX => "search-x",
Send => "send",
Server => "server",
Settings => "settings",
Shapes => "shapes",
Share2 => "share-2",
Shield => "shield",
Shuffle => "shuffle",
SkipBack => "skip-back",
SkipForward => "skip-forward",
SlidersHorizontal => "sliders-horizontal",
Smartphone => "smartphone",
Smile => "smile",
SmilePlus => "smile-plus",
Sparkles => "sparkles",
Spline => "spline",
SquareCheck => "square-check",
Square => "square",
SquareFunction => "square-function",
SquarePen => "square-pen",
Star => "star",
StepForward => "step-forward",
Strikethrough => "strikethrough",
Sun => "sun",
Table => "table",
Tag => "tag",
Target => "target",
Terminal => "terminal",
ThumbsDown => "thumbs-down",
ThumbsUp => "thumbs-up",
Timer => "timer",
Trash2 => "trash-2",
TrendingDown => "trending-down",
TrendingUp => "trending-up",
TriangleAlert => "triangle-alert",
Type => "type",
Underline => "underline",
Undo2 => "undo-2",
Upload => "upload",
User => "user",
UserPlus => "user-plus",
Users => "users",
Variable => "variable",
VideoOff => "video-off",
Video => "video",
Volume2 => "volume-2",
VolumeX => "volume-x",
Wallet => "wallet",
WandSparkles => "wand-sparkles",
WholeWord => "whole-word",
WifiOff => "wifi-off",
Wifi => "wifi",
Workflow => "workflow",
Wrench => "wrench",
X => "x",
Zap => "zap",
ZoomIn => "zoom-in",
ZoomOut => "zoom-out",
}
#[derive(IntoElement)]
pub struct Icon {
name: IconName,
size: IconSize,
color: Option<Hsla>,
hover: Option<(SharedString, Hsla)>,
rotation: Option<Radians>,
scale: f32,
}
impl Icon {
pub fn new(name: IconName) -> Self {
Self {
name,
size: IconSize::Md,
color: None,
hover: None,
rotation: None,
scale: 1.0,
}
}
pub fn size(mut self, size: IconSize) -> Self {
self.size = size;
self
}
/// Defaults to `colors.fg`.
pub fn color(mut self, color: impl Into<Hsla>) -> Self {
self.color = Some(color.into());
self
}
/// Color while the pointer is over `group`.
pub fn group_hover_color(
mut self,
group: impl Into<SharedString>,
color: impl Into<Hsla>,
) -> Self {
self.hover = Some((group.into(), color.into()));
self
}
pub fn rotate(mut self, angle: impl Into<Radians>) -> Self {
self.rotation = Some(angle.into());
self
}
/// Draws the glyph larger or smaller inside the same box.
pub fn scale(mut self, factor: f32) -> Self {
self.scale = factor;
self
}
}
impl From<IconName> for Icon {
fn from(name: IconName) -> Self {
Icon::new(name)
}
}
impl RenderOnce for Icon {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let color = self.color.unwrap_or(theme.colors.fg);
svg()
.path(self.name.path())
.size(theme.icon_size(self.size))
.flex_none()
.text_color(color)
.when_some(self.hover, |svg, (group, hover)| {
svg.group_hover(group, |style| style.text_color(hover))
})
.when(self.rotation.is_some() || self.scale != 1.0, |svg| {
svg.with_transformation(
Transformation::rotate(self.rotation.unwrap_or(Radians(0.0)))
.with_scaling(size(self.scale, self.scale)),
)
})
}
}
#[cfg(test)]
mod tests {
use super::IconName;
#[test]
fn every_icon_ships_in_the_bundle() {
for icon in IconName::ALL {
assert!(
crate::Assets::get(icon.path()).is_some(),
"{icon:?} missing at {}",
icon.path()
);
}
}
}