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