| 1 | // An X11 clock modeled after https://en.wikipedia.org/wiki/Station_clock |
| 2 | // This is a small V example that was based off of the fireworks example. |
| 3 | // Written by Stefan Schroeder in 2021 for the v project examples. |
| 4 | // See LICENSE for license information. |
| 5 | import gg |
| 6 | import math |
| 7 | import time |
| 8 | |
| 9 | // All coordinates are designed for a clock size of this many pixel. |
| 10 | // You cannot change the size of the clock by adjusting this value. |
| 11 | const design_size = 700 |
| 12 | const center = 350 |
| 13 | |
| 14 | // Half the width of a tic-mark. |
| 15 | const tw = 9 |
| 16 | // Height of a minute tic-mark. (hour is twice, 3-hour is thrice) |
| 17 | const th = 25 |
| 18 | // Padding of tic-mark to window border |
| 19 | const tp = 10 |
| 20 | |
| 21 | const tic_color = gg.Color{ |
| 22 | r: 50 |
| 23 | g: 50 |
| 24 | b: 50 |
| 25 | } |
| 26 | const hand_color = gg.black |
| 27 | const second_hand_color = gg.red |
| 28 | |
| 29 | struct App { |
| 30 | minutes_tic []f32 = [f32(center - tw), tp, center + tw, tp, center + tw, tp, center + tw, |
| 31 | tp + 1 * th, center - tw, tp + 1 * th] |
| 32 | hours_tic []f32 = [f32(center - tw), tp, center + tw, tp, center + tw, tp, center + tw, |
| 33 | tp + 2 * th, center - tw, tp + 2 * th] |
| 34 | hours3_tic []f32 = [f32(center - tw), tp, center + tw, tp, center + tw, tp, center + tw, |
| 35 | tp + 3 * th, center - tw, tp + 3 * th] |
| 36 | |
| 37 | hour_hand []f32 = [f32(329), 161, 350, 140, 371, 161, 371, 413, 329, 413] |
| 38 | minute_hand []f32 = [f32(334.25), 40.25, 350, 24.5, 365.75, 40.25, 365.75, 427, 334.25, 427] |
| 39 | second_hand []f32 = [f32(345.8), 38.5, 350, 34.3, 354.2000, 38.5, 358.75, 427, 341.25, 427] |
| 40 | mut: |
| 41 | gg &gg.Context = unsafe { nil } |
| 42 | draw_flag bool = true |
| 43 | dpi_scale f32 = 1.0 |
| 44 | } |
| 45 | |
| 46 | fn on_frame(mut app App) { |
| 47 | if !app.draw_flag { |
| 48 | return |
| 49 | } |
| 50 | app.gg.begin() |
| 51 | |
| 52 | for i in 0 .. 60 { // draw minute tics |
| 53 | draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.minutes_tic, tic_color, i * 6) |
| 54 | } |
| 55 | for i in 0 .. 12 { // hours |
| 56 | draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.hours_tic, tic_color, i * 30) |
| 57 | } |
| 58 | for i in 0 .. 4 { // 3 hours |
| 59 | draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.hours3_tic, tic_color, i * 90) |
| 60 | } |
| 61 | |
| 62 | n := time.now() |
| 63 | |
| 64 | // draw hour hand |
| 65 | i := f32(n.hour) + f32(n.minute) / 60.0 |
| 66 | draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.hour_hand, hand_color, i * 30) |
| 67 | |
| 68 | // draw minute hand |
| 69 | mut j := f32(n.minute) |
| 70 | if n.second == 59 { // make minute hand move smoothly |
| 71 | j += f32(math.sin(f32(n.nanosecond) / 1e9 * math.pi / 2.0)) |
| 72 | } |
| 73 | draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.minute_hand, hand_color, j * 6) |
| 74 | |
| 75 | // draw second hand with smooth transition |
| 76 | k := f32(n.second) + f32(math.sin(f32(n.nanosecond) / 1e9 * math.pi / 2.0)) |
| 77 | draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.second_hand, second_hand_color, |
| 78 | 0 + k * 6) |
| 79 | |
| 80 | app.gg.end() |
| 81 | } |
| 82 | |
| 83 | // Rotate a polygon round the centerpoint |
| 84 | @[manualfree] |
| 85 | fn draw_convex_poly_rotate(mut ctx gg.Context, dpi_scale f32, points []f32, c gg.Color, angle f32) { |
| 86 | sa := math.sin(math.pi * angle / 180.0) |
| 87 | ca := math.cos(math.pi * angle / 180.0) |
| 88 | |
| 89 | mut rotated_points := []f32{cap: points.len} |
| 90 | for i := 0; i < points.len / 2; i++ { |
| 91 | x := points[2 * i] |
| 92 | y := points[2 * i + 1] |
| 93 | xn := f32((x - center) * ca - (y - center) * sa) |
| 94 | yn := f32((x - center) * sa + (y - center) * ca) |
| 95 | rotated_points << (xn + center) * dpi_scale |
| 96 | rotated_points << (yn + center) * dpi_scale |
| 97 | } |
| 98 | ctx.draw_convex_poly(rotated_points, c) |
| 99 | unsafe { rotated_points.free() } |
| 100 | } |
| 101 | |
| 102 | fn (mut app App) resize() { |
| 103 | size := gg.window_size() |
| 104 | // avoid calls when minimized |
| 105 | if size.width < 2 && size.height < 2 { |
| 106 | return |
| 107 | } |
| 108 | w := f32(size.width) / design_size |
| 109 | h := f32(size.height) / design_size |
| 110 | app.dpi_scale = if w < h { w } else { h } |
| 111 | } |
| 112 | |
| 113 | fn on_event(e &gg.Event, mut app App) { |
| 114 | match e.typ { |
| 115 | .resized, .resumed { |
| 116 | app.resize() |
| 117 | } |
| 118 | .iconified { |
| 119 | app.draw_flag = false |
| 120 | } |
| 121 | .restored { |
| 122 | app.draw_flag = true |
| 123 | app.resize() |
| 124 | } |
| 125 | else { |
| 126 | if e.typ == .key_down { |
| 127 | match e.key_code { |
| 128 | .q { |
| 129 | println('Good bye.') |
| 130 | // do we need to free anything here? |
| 131 | app.gg.quit() |
| 132 | } |
| 133 | else {} |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | fn on_init(mut app App) { |
| 141 | app.resize() |
| 142 | } |
| 143 | |
| 144 | fn main() { |
| 145 | println("Press 'q' to quit.") |
| 146 | mut app := &App{} |
| 147 | app.gg = gg.new_context( |
| 148 | width: design_size |
| 149 | height: design_size |
| 150 | window_title: 'Clock!' |
| 151 | bg_color: gg.white |
| 152 | user_data: app |
| 153 | frame_fn: on_frame |
| 154 | event_fn: on_event |
| 155 | init_fn: on_init |
| 156 | ) |
| 157 | app.gg.run() |
| 158 | } |
| 159 | |