Merge feat/icon-set: canvas-drawn icon set replacing emoji
This commit is contained in:
+357
-70
@@ -901,7 +901,14 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
.on_press(AppMessage::OpenLayoutPicker)
|
||||
.style(b_style(color_surface, color_blue, color_text, 6.0))
|
||||
.padding(8),
|
||||
button(text("⚙ Settings").size(14))
|
||||
button(
|
||||
row![
|
||||
icon(IconKind::Settings, 15.0, color_text),
|
||||
text("Settings").size(14),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::alignment::Vertical::Center)
|
||||
)
|
||||
.on_press(AppMessage::NavigateToSettings)
|
||||
.style(b_style(color_surface, color_blue, color_text, 6.0))
|
||||
.padding(8)
|
||||
@@ -937,12 +944,19 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
let mic_test_control: Element<'_, AppMessage> = if in_call {
|
||||
text("Live (in call)").size(11).color(color_green).into()
|
||||
} else {
|
||||
let (label, bg) = if state.mic_test_active {
|
||||
("⏹ Stop mic test", color_red)
|
||||
let (mic_kind, label, bg) = if state.mic_test_active {
|
||||
(IconKind::Stop, "Stop mic test", color_red)
|
||||
} else {
|
||||
("🎙 Test mic", color_surface)
|
||||
(IconKind::Mic, "Test mic", color_surface)
|
||||
};
|
||||
button(text(label).size(12))
|
||||
button(
|
||||
row![
|
||||
icon(mic_kind, 13.0, color_text),
|
||||
text(label).size(12),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::alignment::Vertical::Center)
|
||||
)
|
||||
.on_press(AppMessage::ToggleMicTest(!state.mic_test_active))
|
||||
.style(b_style(bg, color_blue, color_text, 6.0))
|
||||
.padding(6)
|
||||
@@ -1153,11 +1167,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
.padding(10)
|
||||
];
|
||||
|
||||
let create_btn = button(
|
||||
text("Create New Room")
|
||||
.size(16)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
)
|
||||
let create_btn = button(btn_content(IconKind::Create, "Create New Room", color_crust))
|
||||
.on_press(AppMessage::CreatePressed)
|
||||
.style(b_style(color_blue, color_lavender, color_crust, 8.0))
|
||||
.padding(12)
|
||||
@@ -1231,18 +1241,27 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
.size(20)
|
||||
.color(color_blue),
|
||||
horizontal_space(),
|
||||
text(format!("👥 {participant_count} in room"))
|
||||
.size(14)
|
||||
.color(color_subtext),
|
||||
text(format!("⏱ {}", format_duration(call_secs)))
|
||||
.size(14)
|
||||
.color(color_subtext),
|
||||
row![
|
||||
icon(IconKind::People, 15.0, color_subtext),
|
||||
text(format!("{participant_count} in room")).size(14).color(color_subtext),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::alignment::Vertical::Center),
|
||||
row![
|
||||
icon(IconKind::Clock, 15.0, color_subtext),
|
||||
text(format_duration(call_secs)).size(14).color(color_subtext),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::alignment::Vertical::Center),
|
||||
if state.recording {
|
||||
let rec_secs = state.recording_started.map(|t| t.elapsed().as_secs()).unwrap_or(0);
|
||||
container(
|
||||
text(format!("● REC {}", format_duration(rec_secs)))
|
||||
.size(13)
|
||||
.color(color_red)
|
||||
row![
|
||||
icon(IconKind::Record, 12.0, color_red),
|
||||
text(format!("REC {}", format_duration(rec_secs))).size(13).color(color_red),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::alignment::Vertical::Center)
|
||||
)
|
||||
.style(c_style(color_crust, color_red, 6.0))
|
||||
.padding(6)
|
||||
@@ -1253,7 +1272,14 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
text(format!("My ID: {}", &state.self_id[..8]))
|
||||
.size(14)
|
||||
.color(color_subtext),
|
||||
button(text("Copy Ticket").size(12))
|
||||
button(
|
||||
row![
|
||||
icon(IconKind::Copy, 14.0, color_text),
|
||||
text("Copy Ticket").size(12),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::alignment::Vertical::Center)
|
||||
)
|
||||
.on_press(AppMessage::CopyToClipboard)
|
||||
.style(b_style(color_surface, color_blue, color_text, 6.0))
|
||||
.padding(6),
|
||||
@@ -1262,11 +1288,18 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
let el: Element<'_, AppMessage> =
|
||||
if state.config.room_layout == RoomLayout::Drawer {
|
||||
let (lbl, bg, fg) = if state.drawer_chat_open {
|
||||
("💬 Hide chat", color_blue, color_crust)
|
||||
("Hide chat", color_blue, color_crust)
|
||||
} else {
|
||||
("💬 Chat", color_surface, color_text)
|
||||
("Chat", color_surface, color_text)
|
||||
};
|
||||
button(text(lbl).size(12))
|
||||
button(
|
||||
row![
|
||||
icon(IconKind::Chat, 14.0, fg),
|
||||
text(lbl).size(12),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::alignment::Vertical::Center)
|
||||
)
|
||||
.on_press(AppMessage::ToggleDrawerChat)
|
||||
.style(b_style(bg, color_blue, fg, 6.0))
|
||||
.padding(6)
|
||||
@@ -1307,7 +1340,13 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
// Live "you're sharing" badge — only present while sharing.
|
||||
{
|
||||
let el: Element<'_, AppMessage> = if state.self_sharing {
|
||||
text("🔴 Sharing your screen").size(13).color(color_red).into()
|
||||
row![
|
||||
icon(IconKind::Live, 14.0, color_red),
|
||||
text("Sharing your screen").size(13).color(color_red),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::alignment::Vertical::Center)
|
||||
.into()
|
||||
} else {
|
||||
iced::widget::Space::new().width(0.0).height(0.0).into()
|
||||
};
|
||||
@@ -1350,12 +1389,12 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
let is_locally_muted = state.locally_muted.contains(peer_id);
|
||||
|
||||
// Local-mute toggle (silences this peer for us only).
|
||||
let (mute_label, mute_bg, mute_fg) = if is_locally_muted {
|
||||
("🔇", color_red, color_crust)
|
||||
let (mute_kind, mute_bg, mute_fg) = if is_locally_muted {
|
||||
(IconKind::SpeakerOff, color_red, color_crust)
|
||||
} else {
|
||||
("🔊", color_surface, color_text)
|
||||
(IconKind::Speaker, color_surface, color_text)
|
||||
};
|
||||
let mute_btn = button(text(mute_label).size(14))
|
||||
let mute_btn = button(icon(mute_kind, 16.0, mute_fg))
|
||||
.on_press(AppMessage::TogglePeerMute(peer_id_clone))
|
||||
.style(b_style(mute_bg, color_blue, mute_fg, 6.0))
|
||||
.padding(6);
|
||||
@@ -1363,14 +1402,26 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
// Screen-share "Live" badge + Watch button when this peer is sharing.
|
||||
// Watch is enabled only if pixelpass is installed locally.
|
||||
let share_el: Element<'_, AppMessage> = if let Some(ticket) = peer.sharing.clone() {
|
||||
let mut watch_btn = button(text("👁 Watch").size(13))
|
||||
.style(b_style(color_blue, color_lavender, color_crust, 6.0))
|
||||
.padding(6);
|
||||
let mut watch_btn = button(
|
||||
row![
|
||||
icon(IconKind::Eye, 14.0, color_crust),
|
||||
text("Watch").size(13),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::alignment::Vertical::Center),
|
||||
)
|
||||
.style(b_style(color_blue, color_lavender, color_crust, 6.0))
|
||||
.padding(6);
|
||||
if state.pixelpass_available {
|
||||
watch_btn = watch_btn.on_press(AppMessage::WatchShare(ticket));
|
||||
}
|
||||
row![
|
||||
text("🔴 Live").size(13).color(color_red),
|
||||
row![
|
||||
icon(IconKind::Live, 13.0, color_red),
|
||||
text("Live").size(13).color(color_red),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::alignment::Vertical::Center),
|
||||
watch_btn,
|
||||
]
|
||||
.spacing(6)
|
||||
@@ -1456,22 +1507,16 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
let deafen_hover = if state.is_deafened { color_maroon } else { color_blue };
|
||||
let deafen_fg = if state.is_deafened { color_crust } else { color_text };
|
||||
|
||||
let mute_kind = if state.is_muted { IconKind::MicOff } else { IconKind::Mic };
|
||||
let deafen_kind = if state.is_deafened { IconKind::Deafen } else { IconKind::Headphones };
|
||||
let ctrl_buttons = column![
|
||||
button(
|
||||
text(mute_text)
|
||||
.size(16)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
)
|
||||
button(btn_content(mute_kind, mute_text, mute_fg))
|
||||
.on_press(AppMessage::ToggleMutePressed)
|
||||
.style(b_style(mute_bg, mute_hover, mute_fg, 8.0))
|
||||
.padding(14)
|
||||
.width(iced::Length::Fill),
|
||||
vertical_space(10.0),
|
||||
button(
|
||||
text(deafen_text)
|
||||
.size(16)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
)
|
||||
button(btn_content(deafen_kind, deafen_text, deafen_fg))
|
||||
.on_press(AppMessage::ToggleDeafenPressed)
|
||||
.style(b_style(deafen_bg, deafen_hover, deafen_fg, 8.0))
|
||||
.padding(14)
|
||||
@@ -1493,16 +1538,12 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
},
|
||||
vertical_space(20.0),
|
||||
{
|
||||
let (rec_label, rec_bg, rec_hover, rec_fg) = if state.recording {
|
||||
("⏹ Stop Recording", color_red, color_maroon, color_crust)
|
||||
let (rec_kind, rec_label, rec_bg, rec_hover, rec_fg) = if state.recording {
|
||||
(IconKind::Stop, "Stop Recording", color_red, color_maroon, color_crust)
|
||||
} else {
|
||||
("⏺ Record Call", color_surface, color_blue, color_text)
|
||||
(IconKind::Record, "Record Call", color_surface, color_blue, color_text)
|
||||
};
|
||||
button(
|
||||
text(rec_label)
|
||||
.size(16)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
)
|
||||
button(btn_content(rec_kind, rec_label, rec_fg))
|
||||
.on_press(AppMessage::ToggleRecording)
|
||||
.style(b_style(rec_bg, rec_hover, rec_fg, 8.0))
|
||||
.padding(14)
|
||||
@@ -1512,32 +1553,25 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
{
|
||||
// Screen share. Disabled (no on_press) when pixelpass is absent,
|
||||
// with the label saying so — a normal, handled state.
|
||||
let (share_label, share_bg, share_hover, share_fg) = if !state.pixelpass_available {
|
||||
("🖥 Needs pixelpass", color_surface, color_surface, color_subtext)
|
||||
} else if state.self_sharing {
|
||||
("🛑 Stop Sharing", color_red, color_maroon, color_crust)
|
||||
} else {
|
||||
("🖥 Share Screen", color_surface, color_blue, color_text)
|
||||
};
|
||||
let mut share_btn = button(
|
||||
text(share_label)
|
||||
.size(16)
|
||||
.align_x(iced::alignment::Horizontal::Center),
|
||||
)
|
||||
.style(b_style(share_bg, share_hover, share_fg, 8.0))
|
||||
.padding(14)
|
||||
.width(iced::Length::Fill);
|
||||
let (share_kind, share_label, share_bg, share_hover, share_fg) =
|
||||
if !state.pixelpass_available {
|
||||
(IconKind::Monitor, "Needs pixelpass", color_surface, color_surface, color_subtext)
|
||||
} else if state.self_sharing {
|
||||
(IconKind::Stop, "Stop Sharing", color_red, color_maroon, color_crust)
|
||||
} else {
|
||||
(IconKind::Monitor, "Share Screen", color_surface, color_blue, color_text)
|
||||
};
|
||||
let mut share_btn = button(btn_content(share_kind, share_label, share_fg))
|
||||
.style(b_style(share_bg, share_hover, share_fg, 8.0))
|
||||
.padding(14)
|
||||
.width(iced::Length::Fill);
|
||||
if state.pixelpass_available {
|
||||
share_btn = share_btn.on_press(AppMessage::ToggleScreenShare);
|
||||
}
|
||||
share_btn
|
||||
},
|
||||
vertical_space(30.0),
|
||||
button(
|
||||
text("Leave Room")
|
||||
.size(16)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
)
|
||||
button(btn_content(IconKind::Leave, "Leave Room", color_crust))
|
||||
.on_press(AppMessage::LeavePressed)
|
||||
.style(b_style(color_red, color_maroon, color_crust, 8.0))
|
||||
.padding(14)
|
||||
@@ -2186,6 +2220,259 @@ impl Program<AppMessage> for LayoutThumb {
|
||||
}
|
||||
}
|
||||
|
||||
/// The PeerSpeak icon set, drawn on a `canvas` so it needs no image/font asset
|
||||
/// and recolors with the theme (consistent with `LayoutThumb`/`GateMeter`).
|
||||
/// Each icon is authored in a 24×24 space and scaled to the widget size.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum IconKind {
|
||||
Mic,
|
||||
MicOff,
|
||||
Headphones,
|
||||
Deafen,
|
||||
Speaker,
|
||||
SpeakerOff,
|
||||
Monitor,
|
||||
Eye,
|
||||
Record,
|
||||
Stop,
|
||||
Chat,
|
||||
People,
|
||||
Clock,
|
||||
Settings,
|
||||
Copy,
|
||||
Leave,
|
||||
Create,
|
||||
Live,
|
||||
}
|
||||
|
||||
struct Icon {
|
||||
kind: IconKind,
|
||||
color: Color,
|
||||
/// Stroke weight in the 24-unit authoring space (scaled with the widget).
|
||||
weight: f32,
|
||||
}
|
||||
|
||||
/// Build an icon element at `size` px in `color`. Used throughout the room/
|
||||
/// settings UI in place of emoji.
|
||||
fn icon<'a>(kind: IconKind, size: f32, color: Color) -> Element<'a, AppMessage> {
|
||||
Canvas::new(Icon { kind, color, weight: 2.0 })
|
||||
.width(size)
|
||||
.height(size)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Centered "icon + label" content for a full-width control-panel button. The
|
||||
/// icon takes the button's foreground colour so it matches the label.
|
||||
fn btn_content<'a>(kind: IconKind, label: &'a str, color: Color) -> Element<'a, AppMessage> {
|
||||
container(
|
||||
row![icon(kind, 18.0, color), text(label).size(16)]
|
||||
.spacing(8)
|
||||
.align_y(iced::alignment::Vertical::Center),
|
||||
)
|
||||
.center_x(iced::Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
impl Program<AppMessage> for Icon {
|
||||
type State = ();
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &(),
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
use std::f32::consts::PI;
|
||||
let mut f = Frame::new(renderer, bounds.size());
|
||||
let s = bounds.width.min(bounds.height) / 24.0;
|
||||
let col = self.color;
|
||||
let sw = (self.weight * s).max(1.0);
|
||||
let p = |x: f32, y: f32| Point::new(x * s, y * s);
|
||||
let stk = || {
|
||||
canvas::Stroke::default()
|
||||
.with_color(col)
|
||||
.with_width(sw)
|
||||
.with_line_cap(canvas::LineCap::Round)
|
||||
.with_line_join(canvas::LineJoin::Round)
|
||||
};
|
||||
// A stroked polyline (or polygon when `closed`).
|
||||
let poly = |pts: &[(f32, f32)], closed: bool| {
|
||||
Path::new(|b| {
|
||||
for (i, q) in pts.iter().enumerate() {
|
||||
let pt = p(q.0, q.1);
|
||||
if i == 0 {
|
||||
b.move_to(pt);
|
||||
} else {
|
||||
b.line_to(pt);
|
||||
}
|
||||
}
|
||||
if closed {
|
||||
b.close();
|
||||
}
|
||||
})
|
||||
};
|
||||
// A stroked circular arc from a0..a1 radians (0 = +x, sweeps toward +y).
|
||||
let arcp = |cx: f32, cy: f32, r: f32, a0: f32, a1: f32| {
|
||||
Path::new(|b| {
|
||||
b.arc(iced::widget::canvas::path::Arc {
|
||||
center: p(cx, cy),
|
||||
radius: r * s,
|
||||
start_angle: iced::Radians(a0),
|
||||
end_angle: iced::Radians(a1),
|
||||
});
|
||||
})
|
||||
};
|
||||
let rrect = |x: f32, y: f32, w: f32, h: f32, r: f32| {
|
||||
Path::rounded_rectangle(p(x, y), Size::new(w * s, h * s), (r * s).into())
|
||||
};
|
||||
|
||||
match self.kind {
|
||||
IconKind::Mic => {
|
||||
f.stroke(&rrect(9.0, 2.5, 6.0, 11.0, 3.0), stk());
|
||||
f.stroke(&arcp(12.0, 11.0, 6.5, 0.0, PI), stk());
|
||||
f.stroke(&poly(&[(12.0, 17.5), (12.0, 21.0)], false), stk());
|
||||
f.stroke(&poly(&[(8.5, 21.0), (15.5, 21.0)], false), stk());
|
||||
}
|
||||
IconKind::MicOff => {
|
||||
f.stroke(&rrect(9.0, 2.5, 6.0, 11.0, 3.0), stk());
|
||||
f.stroke(&arcp(12.0, 11.0, 6.5, 0.0, PI), stk());
|
||||
f.stroke(&poly(&[(12.0, 17.5), (12.0, 21.0)], false), stk());
|
||||
f.stroke(&poly(&[(8.5, 21.0), (15.5, 21.0)], false), stk());
|
||||
f.stroke(&poly(&[(3.5, 3.5), (20.5, 20.5)], false), stk());
|
||||
}
|
||||
IconKind::Headphones => {
|
||||
f.stroke(&arcp(12.0, 12.5, 8.0, PI, 2.0 * PI), stk());
|
||||
f.stroke(&rrect(3.0, 13.0, 4.5, 7.0, 2.25), stk());
|
||||
f.stroke(&rrect(16.5, 13.0, 4.5, 7.0, 2.25), stk());
|
||||
}
|
||||
IconKind::Deafen => {
|
||||
f.stroke(&arcp(12.0, 12.5, 8.0, PI, 2.0 * PI), stk());
|
||||
f.stroke(&rrect(3.0, 13.0, 4.5, 7.0, 2.25), stk());
|
||||
f.stroke(&rrect(16.5, 13.0, 4.5, 7.0, 2.25), stk());
|
||||
f.stroke(&poly(&[(3.0, 3.0), (21.0, 21.0)], false), stk());
|
||||
}
|
||||
IconKind::Speaker => {
|
||||
f.stroke(
|
||||
&poly(
|
||||
&[
|
||||
(4.0, 9.5),
|
||||
(7.5, 9.5),
|
||||
(13.0, 5.0),
|
||||
(13.0, 19.0),
|
||||
(7.5, 14.5),
|
||||
(4.0, 14.5),
|
||||
],
|
||||
true,
|
||||
),
|
||||
stk(),
|
||||
);
|
||||
f.stroke(&arcp(14.0, 12.0, 4.0, -0.5, 0.5), stk());
|
||||
f.stroke(&arcp(14.0, 12.0, 7.5, -0.65, 0.65), stk());
|
||||
}
|
||||
IconKind::SpeakerOff => {
|
||||
f.stroke(
|
||||
&poly(
|
||||
&[
|
||||
(4.0, 9.5),
|
||||
(7.5, 9.5),
|
||||
(13.0, 5.0),
|
||||
(13.0, 19.0),
|
||||
(7.5, 14.5),
|
||||
(4.0, 14.5),
|
||||
],
|
||||
true,
|
||||
),
|
||||
stk(),
|
||||
);
|
||||
f.stroke(&poly(&[(16.5, 9.5), (21.5, 14.5)], false), stk());
|
||||
f.stroke(&poly(&[(21.5, 9.5), (16.5, 14.5)], false), stk());
|
||||
}
|
||||
IconKind::Monitor => {
|
||||
f.stroke(&rrect(3.0, 4.0, 18.0, 12.0, 2.0), stk());
|
||||
f.stroke(&poly(&[(9.0, 20.0), (15.0, 20.0)], false), stk());
|
||||
f.stroke(&poly(&[(12.0, 16.0), (12.0, 20.0)], false), stk());
|
||||
// up-arrow inside (the "share" cue)
|
||||
f.stroke(&poly(&[(12.0, 13.0), (12.0, 7.5)], false), stk());
|
||||
f.stroke(&poly(&[(9.5, 10.0), (12.0, 7.5), (14.5, 10.0)], false), stk());
|
||||
}
|
||||
IconKind::Eye => {
|
||||
f.stroke(
|
||||
&poly(
|
||||
&[
|
||||
(2.5, 12.0),
|
||||
(6.0, 8.5),
|
||||
(12.0, 7.0),
|
||||
(18.0, 8.5),
|
||||
(21.5, 12.0),
|
||||
(18.0, 15.5),
|
||||
(12.0, 17.0),
|
||||
(6.0, 15.5),
|
||||
],
|
||||
true,
|
||||
),
|
||||
stk(),
|
||||
);
|
||||
f.stroke(&Path::circle(p(12.0, 12.0), 3.0 * s), stk());
|
||||
}
|
||||
IconKind::Record => {
|
||||
f.fill(&Path::circle(p(12.0, 12.0), 6.0 * s), col);
|
||||
}
|
||||
IconKind::Stop => {
|
||||
f.fill(&rrect(6.0, 6.0, 12.0, 12.0, 2.5), col);
|
||||
}
|
||||
IconKind::Chat => {
|
||||
f.stroke(&rrect(3.0, 4.0, 18.0, 12.0, 3.0), stk());
|
||||
f.stroke(&poly(&[(8.0, 16.0), (8.0, 20.5), (12.5, 16.0)], false), stk());
|
||||
}
|
||||
IconKind::People => {
|
||||
f.stroke(&Path::circle(p(9.0, 9.0), 3.2 * s), stk());
|
||||
f.stroke(&arcp(9.0, 20.0, 5.3, PI, 2.0 * PI), stk());
|
||||
f.stroke(&Path::circle(p(16.5, 8.0), 2.7 * s), stk());
|
||||
f.stroke(&arcp(16.5, 20.0, 4.6, PI, 2.0 * PI), stk());
|
||||
}
|
||||
IconKind::Clock => {
|
||||
f.stroke(&Path::circle(p(12.0, 12.0), 8.5 * s), stk());
|
||||
f.stroke(&poly(&[(12.0, 7.0), (12.0, 12.0)], false), stk());
|
||||
f.stroke(&poly(&[(12.0, 12.0), (15.5, 14.0)], false), stk());
|
||||
}
|
||||
IconKind::Settings => {
|
||||
f.stroke(&poly(&[(4.0, 7.0), (20.0, 7.0)], false), stk());
|
||||
f.stroke(&poly(&[(4.0, 12.0), (20.0, 12.0)], false), stk());
|
||||
f.stroke(&poly(&[(4.0, 17.0), (20.0, 17.0)], false), stk());
|
||||
f.fill(&Path::circle(p(15.0, 7.0), 2.4 * s), col);
|
||||
f.fill(&Path::circle(p(9.0, 12.0), 2.4 * s), col);
|
||||
f.fill(&Path::circle(p(16.0, 17.0), 2.4 * s), col);
|
||||
}
|
||||
IconKind::Copy => {
|
||||
f.stroke(&rrect(8.0, 8.0, 12.0, 12.0, 2.0), stk());
|
||||
f.stroke(&rrect(4.0, 4.0, 12.0, 12.0, 2.0), stk());
|
||||
}
|
||||
IconKind::Leave => {
|
||||
f.stroke(
|
||||
&poly(&[(9.0, 4.0), (6.0, 4.0), (6.0, 20.0), (9.0, 20.0)], false),
|
||||
stk(),
|
||||
);
|
||||
f.stroke(&poly(&[(9.5, 12.0), (20.0, 12.0)], false), stk());
|
||||
f.stroke(&poly(&[(15.5, 8.0), (20.0, 12.0), (15.5, 16.0)], false), stk());
|
||||
}
|
||||
IconKind::Create => {
|
||||
f.stroke(&Path::circle(p(12.0, 12.0), 8.5 * s), stk());
|
||||
f.stroke(&poly(&[(12.0, 8.0), (12.0, 16.0)], false), stk());
|
||||
f.stroke(&poly(&[(8.0, 12.0), (16.0, 12.0)], false), stk());
|
||||
}
|
||||
IconKind::Live => {
|
||||
f.fill(&Path::circle(p(12.0, 12.0), 3.0 * s), col);
|
||||
f.stroke(&arcp(12.0, 12.0, 5.0, -0.4 * PI, 0.4 * PI), stk());
|
||||
f.stroke(&arcp(12.0, 12.0, 5.0, 0.6 * PI, 1.4 * PI), stk());
|
||||
f.stroke(&arcp(12.0, 12.0, 8.0, -0.32 * PI, 0.32 * PI), stk());
|
||||
f.stroke(&arcp(12.0, 12.0, 8.0, 0.68 * PI, 1.32 * PI), stk());
|
||||
}
|
||||
}
|
||||
vec![f.into_geometry()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{format_duration, reconnect_attempt_chime, reconnected_chime, GateMeter, METER_MAX};
|
||||
|
||||
Reference in New Issue
Block a user