Added blinking cursor

This commit is contained in:
Jonathan Johnson 2023-10-25 09:08:53 -07:00
parent dc6c22372b
commit 69f6f68ba6
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
2 changed files with 100 additions and 23 deletions

35
Cargo.lock generated
View file

@ -35,13 +35,14 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.8.3" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
"version_check", "version_check",
"zerocopy",
] ]
[[package]] [[package]]
@ -95,7 +96,7 @@ dependencies = [
[[package]] [[package]]
name = "appit" name = "appit"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/khonsulabs/appit#665107f59b0d1b1cc6780800fa1e172faf615f07" source = "git+https://github.com/khonsulabs/appit#91c540c2a2db69eb25ea47eccb7aac1eb911933e"
dependencies = [ dependencies = [
"raw-window-handle 0.5.2", "raw-window-handle 0.5.2",
"winit", "winit",
@ -1607,9 +1608,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.3" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
@ -1881,9 +1882,9 @@ dependencies = [
[[package]] [[package]]
name = "web-time" name = "web-time"
version = "0.2.2" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8208e3fdbc243c8fd30805721869242a7f6de3e2e9f3b057652ab36e52ae1e87" checksum = "57099a701fb3a8043f993e8228dc24229c7b942e2b009a1b962e54489ba1d3bf"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@ -2313,3 +2314,23 @@ name = "zeno"
version = "0.2.3" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
[[package]]
name = "zerocopy"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81ba595b9f2772fbee2312de30eeb80ec773b4cb2f1e8098db024afadda6c06f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "772666c41fb6dceaf520b564b962d738a8e1a83b41bd48945f50837aed78bb1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -1,5 +1,6 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt::Debug; use std::fmt::Debug;
use std::time::Duration;
use kludgine::app::winit::event::Ime; use kludgine::app::winit::event::Ime;
use kludgine::app::winit::keyboard::Key; use kludgine::app::winit::keyboard::Key;
@ -17,10 +18,13 @@ use crate::styles::{HighlightColor, LineHeight, Styles, TextColor, TextSize};
use crate::utils::ModifiersExt; use crate::utils::ModifiersExt;
use crate::widget::{EventHandling, IntoValue, Value, Widget, HANDLED, UNHANDLED}; use crate::widget::{EventHandling, IntoValue, Value, Widget, HANDLED, UNHANDLED};
const CURSOR_BLINK_DURATION: Duration = Duration::from_millis(500);
#[must_use] #[must_use]
pub struct Input { pub struct Input {
pub text: Value<String>, pub text: Value<String>,
editor: Option<LiveEditor>, editor: Option<LiveEditor>,
cursor_state: CursorState,
} }
impl Input { impl Input {
@ -32,6 +36,7 @@ impl Input {
Self { Self {
text: initial_text.into_value(), text: initial_text.into_value(),
editor: None, editor: None,
cursor_state: CursorState::default(),
} }
} }
@ -121,11 +126,14 @@ impl Widget for Input {
y: location.y.0, y: location.y.0,
}, },
); );
self.cursor_state.force_on();
context.set_needs_redraw(); context.set_needs_redraw();
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn redraw(&mut self, context: &mut crate::context::GraphicsContext<'_, '_, '_, '_, '_>) { fn redraw(&mut self, context: &mut crate::context::GraphicsContext<'_, '_, '_, '_, '_>) {
self.cursor_state.update(context.elapsed());
let cursor_state = self.cursor_state;
let size = context.graphics.size(); let size = context.graphics.size();
let styles = context.query_style(&[&TextColor, &HighlightColor]); let styles = context.query_style(&[&TextColor, &HighlightColor]);
let highlight = styles.get_or_default(&HighlightColor); let highlight = styles.get_or_default(&HighlightColor);
@ -246,18 +254,21 @@ impl Widget for Input {
(Err(_), Err(_)) => {} (Err(_), Err(_)) => {}
} }
} else if let Ok((location, _)) = cursor_glyph(buffer, &cursor) { } else if let Ok((location, _)) = cursor_glyph(buffer, &cursor) {
context.graphics.draw_shape( if cursor_state.visible {
&Shape::filled_rect( context.graphics.draw_shape(
Rect::new( &Shape::filled_rect(
location, Rect::new(
Size::new(Px(1), line_height), location,
Size::new(Px(1), line_height),
),
highlight, // TODO cursor should be a bold color, highlight probably not. This should have its own color.
), ),
highlight, // TODO cursor should be a bold color, highlight probably not. This should have its own color. Point::default(),
), None,
Point::default(), None,
None, );
None, }
); context.redraw_in(cursor_state.remaining_until_blink);
} }
} }
@ -309,7 +320,7 @@ impl Widget for Input {
"Keyboard input: {:?}. {:?}, {:?}", "Keyboard input: {:?}. {:?}, {:?}",
input.logical_key, input.text, input.physical_key input.logical_key, input.text, input.physical_key
); );
match (input.logical_key, input.text) { let handled = match (input.logical_key, input.text) {
(key @ (Key::Backspace | Key::Delete), _) => { (key @ (Key::Backspace | Key::Delete), _) => {
editor.action( editor.action(
context.kludgine.font_system(), context.kludgine.font_system(),
@ -319,7 +330,6 @@ impl Widget for Input {
_ => unreachable!("previously matched"), _ => unreachable!("previously matched"),
}, },
); );
context.set_needs_redraw();
HANDLED HANDLED
} }
(key @ (Key::ArrowLeft | Key::ArrowDown | Key::ArrowUp | Key::ArrowRight), _) => { (key @ (Key::ArrowLeft | Key::ArrowDown | Key::ArrowUp | Key::ArrowRight), _) => {
@ -346,16 +356,21 @@ impl Widget for Input {
_ => unreachable!("previously matched"), _ => unreachable!("previously matched"),
}, },
); );
context.set_needs_redraw();
HANDLED HANDLED
} }
(_, Some(text)) if !context.modifiers().state().primary() => { (_, Some(text)) if !context.modifiers().state().primary() => {
editor.insert_string(&text, None); editor.insert_string(&text, None);
context.set_needs_redraw();
HANDLED HANDLED
} }
(_, _) => UNHANDLED, (_, _) => UNHANDLED,
};
if handled.is_break() {
context.set_needs_redraw();
self.cursor_state.force_on();
} }
handled
} }
fn ime(&mut self, ime: Ime, context: &mut EventContext<'_, '_>) -> EventHandling { fn ime(&mut self, ime: Ime, context: &mut EventContext<'_, '_>) -> EventHandling {
@ -379,7 +394,6 @@ impl Widget for Input {
} }
fn blur(&mut self, context: &mut EventContext<'_, '_>) { fn blur(&mut self, context: &mut EventContext<'_, '_>) {
println!("Blur");
context.set_ime_allowed(false); context.set_ime_allowed(false);
} }
} }
@ -459,3 +473,45 @@ enum NotVisible {
Before, Before,
After, After,
} }
#[derive(Clone, Copy)]
struct CursorState {
visible: bool,
remaining_until_blink: Duration,
}
impl Default for CursorState {
fn default() -> Self {
Self {
visible: true,
remaining_until_blink: CURSOR_BLINK_DURATION,
}
}
}
impl CursorState {
pub fn update(&mut self, elapsed: Duration) {
let total_cycles = elapsed.as_nanos() / CURSOR_BLINK_DURATION.as_nanos();
let remaining = Duration::from_nanos(
u64::try_from(elapsed.as_nanos() % CURSOR_BLINK_DURATION.as_nanos())
.expect("remainder fits in u64"),
);
// If we have an odd number of totaal cycles, flip the visibility.
if total_cycles & 1 == 1 {
self.visible = !self.visible;
}
if let Some(remaining) = self.remaining_until_blink.checked_sub(remaining) {
self.remaining_until_blink = remaining;
} else {
self.visible = !self.visible;
self.remaining_until_blink =
CURSOR_BLINK_DURATION - (remaining - self.remaining_until_blink);
}
}
pub fn force_on(&mut self) {
self.visible = true;
self.remaining_until_blink = CURSOR_BLINK_DURATION;
}
}