aboutsummaryrefslogtreecommitdiff
path: root/users/bcat/bcat_oled.c
diff options
context:
space:
mode:
Diffstat (limited to 'users/bcat/bcat_oled.c')
-rw-r--r--users/bcat/bcat_oled.c216
1 files changed, 216 insertions, 0 deletions
diff --git a/users/bcat/bcat_oled.c b/users/bcat/bcat_oled.c
new file mode 100644
index 000000000..390c9127b
--- /dev/null
+++ b/users/bcat/bcat_oled.c
@@ -0,0 +1,216 @@
1/* Copyright 2021 Jonathan Rascher
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "bcat_oled.h"
18
19#include "quantum.h"
20#include "bcat.h"
21
22#if defined(BCAT_OLED_PET)
23# include "bcat_oled_pet.h"
24#endif
25
26#define TRIANGLE_UP 0x1e
27#define TRIANGLE_DOWN 0x1f
28
29#if defined(BCAT_OLED_PET)
30static bool oled_pet_should_jump = false;
31#endif
32
33/* Should be overridden by the keymap to render the OLED contents. For split
34 * keyboards, this function is only called on the master side.
35 */
36__attribute__((weak)) void oled_task_keymap(const oled_keyboard_state_t *keyboard_state) {}
37
38bool oled_task_user(void) {
39#if defined(SPLIT_KEYBOARD)
40 if (is_keyboard_master()) {
41#endif
42 /* Custom OLED timeout implementation that only considers user activity.
43 * Allows the OLED to turn off in the middle of a continuous animation.
44 */
45 static const uint16_t TIMEOUT_MILLIS = 60000 /* 1 min */;
46
47 if (last_input_activity_elapsed() < TIMEOUT_MILLIS) {
48 if (!is_oled_on()) {
49 oled_on();
50 }
51 oled_keyboard_state_t keyboard_state = {
52 .mods = get_mods(),
53 .leds = host_keyboard_led_state(),
54 .wpm = get_current_wpm(),
55 };
56 oled_task_keymap(&keyboard_state);
57 } else if (is_oled_on()) {
58 oled_off();
59 }
60#if defined(SPLIT_KEYBOARD)
61 } else {
62 /* Display logo embedded at standard location in the OLED font on the
63 * slave side. By default, this is a "QMK firmware" logo, but many
64 * keyboards substitute their own logo. Occupies 21x3 character cells.
65 *
66 * Since the slave display buffer never changes, we don't need to worry
67 * about oled_render incorrectly turning the OLED on. Instead, we rely
68 * on SPLIT_OLED_ENABLE to propagate OLED on/off status from master.
69 */
70 static const char PROGMEM logo[] = {
71 // clang-format off
72 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94,
73 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4,
74 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4,
75 0x00,
76 // clang-format on
77 };
78
79 oled_write_P(logo, /*invert=*/false);
80 }
81#endif
82
83 return false;
84}
85
86void render_oled_layers(void) {
87 oled_advance_char();
88 oled_advance_char();
89#if defined(BCAT_ORTHO_LAYERS)
90 oled_write_char(IS_LAYER_ON(LAYER_LOWER) ? TRIANGLE_DOWN : ' ', /*invert=*/false);
91 oled_advance_char();
92 oled_write_char(IS_LAYER_ON(LAYER_RAISE) ? TRIANGLE_UP : ' ', /*invert=*/false);
93#else
94 switch (get_highest_layer(layer_state)) {
95 case LAYER_FUNCTION_1:
96 oled_write_P(PSTR("FN1"), /*invert=*/false);
97 break;
98 case LAYER_FUNCTION_2:
99 oled_write_P(PSTR("FN2"), /*invert=*/false);
100 break;
101 default:
102 oled_write_P(PSTR(" "), /*invert=*/false);
103 break;
104 }
105#endif
106}
107
108void render_oled_indicators(led_t leds) {
109 oled_advance_char();
110 oled_advance_char();
111 oled_write_P(leds.num_lock ? PSTR("NUM") : PSTR(" "), /*invert=*/false);
112 oled_advance_char();
113 oled_advance_char();
114 oled_write_P(leds.caps_lock ? PSTR("CAP") : PSTR(" "), /*invert=*/false);
115 oled_advance_char();
116 oled_advance_char();
117 oled_write_P(leds.scroll_lock ? PSTR("SCR") : PSTR(" "), /*invert=*/false);
118}
119
120void render_oled_wpm(uint8_t wpm) {
121 static const uint16_t UPDATE_MILLIS = 100;
122 static uint32_t update_timeout = 0;
123
124 if (timer_expired32(timer_read32(), update_timeout)) {
125 oled_advance_char();
126 oled_advance_char();
127 oled_write_P(wpm > 0 ? PSTR("WPM") : PSTR(" "), /*invert=*/false);
128 if (wpm > 0) {
129 oled_advance_char();
130 oled_advance_char();
131 oled_write(get_u8_str(wpm, ' '), /*invert=*/false);
132 } else {
133 oled_advance_page(/*clearPageRemainder=*/true);
134 }
135
136 update_timeout = timer_read32() + UPDATE_MILLIS;
137 }
138}
139
140#if defined(BCAT_OLED_PET)
141void process_record_oled(uint16_t keycode, const keyrecord_t *record) {
142 switch (keycode) {
143 case KC_SPACE:
144 if (oled_pet_can_jump()) {
145 oled_pet_should_jump = record->event.pressed;
146 }
147 break;
148 default:
149 break;
150 }
151}
152
153static void redraw_oled_pet(uint8_t col, uint8_t line, bool jumping, oled_pet_state_t state) {
154 oled_set_cursor(col, line);
155 if (jumping) {
156 oled_write_raw_P(oled_pet_frame(state), oled_pet_frame_bytes());
157 oled_set_cursor(col, line + oled_pet_frame_lines());
158 oled_advance_page(/*clearPageRemainder=*/true);
159 } else {
160 oled_advance_page(/*clearPageRemainder=*/true);
161 oled_write_raw_P(oled_pet_frame(state), oled_pet_frame_bytes());
162 }
163}
164
165void render_oled_pet(uint8_t col, uint8_t line, const oled_keyboard_state_t *keyboard_state) {
166 /* Current animation to draw. We track changes to avoid redrawing the same
167 * frame repeatedly, allowing oled_pet_post_render to draw over the
168 * animation frame.
169 */
170 static oled_pet_state_t state = 0;
171 static bool state_changed = true;
172
173 /* Minimum time until the pet comes down after jumping. */
174 static const uint16_t JUMP_MILLIS = 200;
175 static bool jumping = false;
176
177 /* Time until the next animation or jump state change. */
178 static uint32_t update_timeout = 0;
179 static uint32_t jump_timeout = 0;
180
181 /* If the user pressed the jump key, immediately redraw instead of waiting
182 * for the animation frame to update. That way, the pet appears to respond
183 * to jump commands quickly rather than lagging. If the user released the
184 * jump key, wait for the jump timeout to avoid overly brief jumps.
185 */
186 bool redraw = state_changed;
187 if (oled_pet_should_jump && !jumping) {
188 redraw = true;
189 jumping = true;
190 jump_timeout = timer_read32() + JUMP_MILLIS;
191 } else if (!oled_pet_should_jump && jumping && timer_expired32(timer_read32(), jump_timeout)) {
192 redraw = true;
193 jumping = false;
194 }
195
196 /* Draw the actual animation, then move the cursor to the end of the
197 * rendered area. (Note that we take up an extra line to account for
198 * jumping, which shifts the animation up or down a line.)
199 */
200 if (redraw) {
201 redraw_oled_pet(col, line, jumping, state);
202 }
203 oled_pet_post_render(col, line + !jumping, keyboard_state, redraw);
204 oled_set_cursor(col, line + oled_pet_frame_lines() + 1);
205
206 /* If the update timer expired, recompute the pet's animation state. */
207 if (timer_expired32(timer_read32(), update_timeout)) {
208 oled_pet_state_t new_state = oled_pet_next_state(state, keyboard_state);
209 state_changed = new_state != state;
210 state = new_state;
211 update_timeout = timer_read32() + oled_pet_update_millis(keyboard_state);
212 } else {
213 state_changed = false;
214 }
215}
216#endif