mirror of
https://github.com/danbulant/cushy
synced 2026-06-06 08:01:48 +00:00
Added blinking cursor
This commit is contained in:
parent
dc6c22372b
commit
69f6f68ba6
2 changed files with 100 additions and 23 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue