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]]
name = "ahash"
version = "0.8.3"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
@ -95,7 +96,7 @@ dependencies = [
[[package]]
name = "appit"
version = "0.1.0"
source = "git+https://github.com/khonsulabs/appit#665107f59b0d1b1cc6780800fa1e172faf615f07"
source = "git+https://github.com/khonsulabs/appit#91c540c2a2db69eb25ea47eccb7aac1eb911933e"
dependencies = [
"raw-window-handle 0.5.2",
"winit",
@ -1607,9 +1608,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml_datetime"
version = "0.6.3"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
[[package]]
name = "toml_edit"
@ -1881,9 +1882,9 @@ dependencies = [
[[package]]
name = "web-time"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8208e3fdbc243c8fd30805721869242a7f6de3e2e9f3b057652ab36e52ae1e87"
checksum = "57099a701fb3a8043f993e8228dc24229c7b942e2b009a1b962e54489ba1d3bf"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -2313,3 +2314,23 @@ name = "zeno"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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::fmt::Debug;
use std::time::Duration;
use kludgine::app::winit::event::Ime;
use kludgine::app::winit::keyboard::Key;
@ -17,10 +18,13 @@ use crate::styles::{HighlightColor, LineHeight, Styles, TextColor, TextSize};
use crate::utils::ModifiersExt;
use crate::widget::{EventHandling, IntoValue, Value, Widget, HANDLED, UNHANDLED};
const CURSOR_BLINK_DURATION: Duration = Duration::from_millis(500);
#[must_use]
pub struct Input {
pub text: Value<String>,
editor: Option<LiveEditor>,
cursor_state: CursorState,
}
impl Input {
@ -32,6 +36,7 @@ impl Input {
Self {
text: initial_text.into_value(),
editor: None,
cursor_state: CursorState::default(),
}
}
@ -121,11 +126,14 @@ impl Widget for Input {
y: location.y.0,
},
);
self.cursor_state.force_on();
context.set_needs_redraw();
}
#[allow(clippy::too_many_lines)]
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 styles = context.query_style(&[&TextColor, &HighlightColor]);
let highlight = styles.get_or_default(&HighlightColor);
@ -246,18 +254,21 @@ impl Widget for Input {
(Err(_), Err(_)) => {}
}
} else if let Ok((location, _)) = cursor_glyph(buffer, &cursor) {
context.graphics.draw_shape(
&Shape::filled_rect(
Rect::new(
location,
Size::new(Px(1), line_height),
if cursor_state.visible {
context.graphics.draw_shape(
&Shape::filled_rect(
Rect::new(
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,
None,
);
Point::default(),
None,
None,
);
}
context.redraw_in(cursor_state.remaining_until_blink);
}
}
@ -309,7 +320,7 @@ impl Widget for Input {
"Keyboard input: {:?}. {:?}, {:?}",
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), _) => {
editor.action(
context.kludgine.font_system(),
@ -319,7 +330,6 @@ impl Widget for Input {
_ => unreachable!("previously matched"),
},
);
context.set_needs_redraw();
HANDLED
}
(key @ (Key::ArrowLeft | Key::ArrowDown | Key::ArrowUp | Key::ArrowRight), _) => {
@ -346,16 +356,21 @@ impl Widget for Input {
_ => unreachable!("previously matched"),
},
);
context.set_needs_redraw();
HANDLED
}
(_, Some(text)) if !context.modifiers().state().primary() => {
editor.insert_string(&text, None);
context.set_needs_redraw();
HANDLED
}
(_, _) => 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 {
@ -379,7 +394,6 @@ impl Widget for Input {
}
fn blur(&mut self, context: &mut EventContext<'_, '_>) {
println!("Blur");
context.set_ime_allowed(false);
}
}
@ -459,3 +473,45 @@ enum NotVisible {
Before,
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;
}
}