| 1 | //! Custom SVG-style icons rendered with Cairo |
| 2 | //! |
| 3 | //! Clean, minimal icon designs for power controls. |
| 4 | |
| 5 | use cairo::Context; |
| 6 | use std::f64::consts::PI; |
| 7 | |
| 8 | /// Icon size (icons are designed for this size and scale proportionally) |
| 9 | pub const ICON_SIZE: f64 = 24.0; |
| 10 | |
| 11 | /// Draw a power/shutdown icon |
| 12 | /// A circle with a vertical line at the top (power symbol) |
| 13 | pub fn draw_power(ctx: &Context, x: f64, y: f64, size: f64) { |
| 14 | let scale = size / ICON_SIZE; |
| 15 | let cx = x + size / 2.0; |
| 16 | let cy = y + size / 2.0; |
| 17 | let radius = 9.0 * scale; |
| 18 | let stroke_width = 2.0 * scale; |
| 19 | |
| 20 | ctx.set_line_width(stroke_width); |
| 21 | ctx.set_line_cap(cairo::LineCap::Round); |
| 22 | |
| 23 | // Circle (arc from ~60° to ~300°, leaving gap at top) |
| 24 | let gap_angle = PI / 6.0; // 30 degrees gap on each side of top |
| 25 | ctx.arc(cx, cy, radius, PI / 2.0 + gap_angle, 5.0 * PI / 2.0 - gap_angle); |
| 26 | ctx.stroke().ok(); |
| 27 | |
| 28 | // Vertical line from top (outside) going down toward center |
| 29 | ctx.move_to(cx, cy - radius - stroke_width); // Start above the circle gap |
| 30 | ctx.line_to(cx, cy); // Go down to center |
| 31 | ctx.stroke().ok(); |
| 32 | } |
| 33 | |
| 34 | /// Draw a reboot/restart icon |
| 35 | /// Two curved arrows forming a circle |
| 36 | pub fn draw_reboot(ctx: &Context, x: f64, y: f64, size: f64) { |
| 37 | let scale = size / ICON_SIZE; |
| 38 | let cx = x + size / 2.0; |
| 39 | let cy = y + size / 2.0; |
| 40 | let radius = 8.0 * scale; |
| 41 | let stroke_width = 2.0 * scale; |
| 42 | let arrow_size = 4.0 * scale; |
| 43 | |
| 44 | ctx.set_line_width(stroke_width); |
| 45 | ctx.set_line_cap(cairo::LineCap::Round); |
| 46 | ctx.set_line_join(cairo::LineJoin::Round); |
| 47 | |
| 48 | // Top arc (right side, going clockwise) |
| 49 | ctx.arc(cx, cy, radius, -PI * 0.75, PI * 0.1); |
| 50 | ctx.stroke().ok(); |
| 51 | |
| 52 | // Top arrow head (pointing right/down) |
| 53 | let arrow_x = cx + radius * (PI * 0.1).cos(); |
| 54 | let arrow_y = cy + radius * (PI * 0.1).sin(); |
| 55 | ctx.move_to(arrow_x - arrow_size, arrow_y - arrow_size * 0.5); |
| 56 | ctx.line_to(arrow_x, arrow_y); |
| 57 | ctx.line_to(arrow_x - arrow_size * 0.5, arrow_y + arrow_size); |
| 58 | ctx.stroke().ok(); |
| 59 | |
| 60 | // Bottom arc (left side, going clockwise) |
| 61 | ctx.arc(cx, cy, radius, PI * 0.25, PI * 1.1); |
| 62 | ctx.stroke().ok(); |
| 63 | |
| 64 | // Bottom arrow head (pointing left/up) |
| 65 | let arrow_x = cx + radius * (PI * 1.1).cos(); |
| 66 | let arrow_y = cy + radius * (PI * 1.1).sin(); |
| 67 | ctx.move_to(arrow_x + arrow_size, arrow_y + arrow_size * 0.5); |
| 68 | ctx.line_to(arrow_x, arrow_y); |
| 69 | ctx.line_to(arrow_x + arrow_size * 0.5, arrow_y - arrow_size); |
| 70 | ctx.stroke().ok(); |
| 71 | } |
| 72 | |
| 73 | /// Draw a suspend/sleep icon |
| 74 | /// A crescent moon |
| 75 | pub fn draw_suspend(ctx: &Context, x: f64, y: f64, size: f64) { |
| 76 | let scale = size / ICON_SIZE; |
| 77 | let cx = x + size / 2.0; |
| 78 | let cy = y + size / 2.0; |
| 79 | let outer_radius = 9.0 * scale; |
| 80 | let inner_radius = 7.0 * scale; |
| 81 | let offset = 4.0 * scale; // How much the inner circle is offset |
| 82 | |
| 83 | // Create crescent by subtracting inner circle from outer |
| 84 | // Outer circle (full moon) |
| 85 | ctx.arc(cx, cy, outer_radius, 0.0, 2.0 * PI); |
| 86 | |
| 87 | // Inner circle (shadow) - offset to create crescent |
| 88 | ctx.arc_negative(cx + offset, cy - offset * 0.3, inner_radius, 2.0 * PI, 0.0); |
| 89 | |
| 90 | ctx.fill().ok(); |
| 91 | } |
| 92 | |
| 93 | /// Draw a dropdown/chevron icon |
| 94 | /// A simple downward-pointing chevron |
| 95 | pub fn draw_chevron_down(ctx: &Context, x: f64, y: f64, size: f64) { |
| 96 | let scale = size / ICON_SIZE; |
| 97 | let cx = x + size / 2.0; |
| 98 | let cy = y + size / 2.0; |
| 99 | let half_width = 5.0 * scale; |
| 100 | let half_height = 3.0 * scale; |
| 101 | let stroke_width = 2.0 * scale; |
| 102 | |
| 103 | ctx.set_line_width(stroke_width); |
| 104 | ctx.set_line_cap(cairo::LineCap::Round); |
| 105 | ctx.set_line_join(cairo::LineJoin::Round); |
| 106 | |
| 107 | ctx.move_to(cx - half_width, cy - half_height); |
| 108 | ctx.line_to(cx, cy + half_height); |
| 109 | ctx.line_to(cx + half_width, cy - half_height); |
| 110 | ctx.stroke().ok(); |
| 111 | } |
| 112 | |
| 113 | /// Draw a checkmark icon (for selected session) |
| 114 | pub fn draw_checkmark(ctx: &Context, x: f64, y: f64, size: f64) { |
| 115 | let scale = size / ICON_SIZE; |
| 116 | let stroke_width = 2.5 * scale; |
| 117 | |
| 118 | ctx.set_line_width(stroke_width); |
| 119 | ctx.set_line_cap(cairo::LineCap::Round); |
| 120 | ctx.set_line_join(cairo::LineJoin::Round); |
| 121 | |
| 122 | // Checkmark shape |
| 123 | ctx.move_to(x + 4.0 * scale, y + 12.0 * scale); |
| 124 | ctx.line_to(x + 9.0 * scale, y + 17.0 * scale); |
| 125 | ctx.line_to(x + 20.0 * scale, y + 6.0 * scale); |
| 126 | ctx.stroke().ok(); |
| 127 | } |
| 128 | |
| 129 | /// Draw a user/person icon |
| 130 | pub fn draw_user(ctx: &Context, x: f64, y: f64, size: f64) { |
| 131 | let scale = size / ICON_SIZE; |
| 132 | let cx = x + size / 2.0; |
| 133 | let cy = y + size / 2.0; |
| 134 | |
| 135 | // Head (circle) |
| 136 | let head_radius = 5.0 * scale; |
| 137 | let head_y = cy - 3.0 * scale; |
| 138 | ctx.arc(cx, head_y, head_radius, 0.0, 2.0 * PI); |
| 139 | ctx.fill().ok(); |
| 140 | |
| 141 | // Body (rounded rectangle / shoulders) |
| 142 | let body_width = 14.0 * scale; |
| 143 | let body_height = 8.0 * scale; |
| 144 | let body_y = cy + 4.0 * scale; |
| 145 | let body_radius = 4.0 * scale; |
| 146 | |
| 147 | ctx.new_sub_path(); |
| 148 | ctx.arc(cx - body_width / 2.0 + body_radius, body_y + body_radius, body_radius, PI, 1.5 * PI); |
| 149 | ctx.arc(cx + body_width / 2.0 - body_radius, body_y + body_radius, body_radius, 1.5 * PI, 2.0 * PI); |
| 150 | ctx.line_to(cx + body_width / 2.0, body_y + body_height); |
| 151 | ctx.line_to(cx - body_width / 2.0, body_y + body_height); |
| 152 | ctx.close_path(); |
| 153 | ctx.fill().ok(); |
| 154 | } |
| 155 |