Skip to content

Commit 5708713

Browse files
committed
feat: implement custom text cursor engine with multiple shapes
- Replaced standard egui cursor with a custom rendering engine in the editor. - Added support for Bar, Block (semi-transparent), and Underscore shapes. - Implemented manual blink logic synchronized with application time. - Integrated cursor settings with the immediate-mode UI for instant feedback. - Fixed an issue where cursor style changes were not correctly applied to the TextEdit widget.
1 parent e3454c4 commit 5708713

2 files changed

Lines changed: 62 additions & 0 deletions

File tree

src/ui_editor.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ impl EditorApp {
302302

303303
let min_height = (text_area_rect.height() - scrollbar_outer_margin).max(100.0);
304304

305+
// HACK: Hide default cursor to draw our own
306+
let original_cursor_color = ui.visuals().text_cursor.stroke.color;
307+
ui.visuals_mut().text_cursor.stroke.color = egui::Color32::TRANSPARENT;
308+
305309
let output = egui::TextEdit::multiline(text_ptr)
306310
.id(text_edit_id)
307311
.font(egui::TextStyle::Monospace)
@@ -325,6 +329,60 @@ impl EditorApp {
325329
})
326330
.show(ui);
327331

332+
// Restore cursor color for other widgets if needed (though ui is local)
333+
ui.visuals_mut().text_cursor.stroke.color = original_cursor_color;
334+
335+
// Draw Custom Cursor
336+
if ui.memory(|mem| mem.has_focus(text_edit_id)) {
337+
if let Some(cursor_range) = output.cursor_range {
338+
let cursor_pos = cursor_range.primary;
339+
let galley = &output.galley;
340+
341+
// Get cursor position relative to galley
342+
let cursor_rect = galley.pos_from_cursor(cursor_pos);
343+
344+
// Screen-space rect for the cursor
345+
let painter_rect = cursor_rect.translate(output.galley_pos.to_vec2());
346+
347+
// Check blink state
348+
let is_visible = if self.settings.cursor_blink {
349+
// Use simple math for blink (0.5s intervals)
350+
(ui.input(|i| i.time) * 2.0) as i32 % 2 == 0
351+
} else {
352+
true
353+
};
354+
355+
if is_visible {
356+
let color = original_cursor_color;
357+
match self.settings.cursor_shape {
358+
crate::settings::CursorShape::Bar => {
359+
ui.painter().line_segment(
360+
[painter_rect.left_top(), painter_rect.left_bottom()],
361+
egui::Stroke::new(2.0, color),
362+
);
363+
}
364+
crate::settings::CursorShape::Block => {
365+
// Block cursor: a rectangle covering the character width
366+
let char_width = editor_font_size * 0.6;
367+
let block_rect = egui::Rect::from_min_size(
368+
painter_rect.min,
369+
egui::vec2(char_width, painter_rect.height())
370+
);
371+
ui.painter().rect_filled(block_rect, 0.0, color.linear_multiply(0.7));
372+
}
373+
crate::settings::CursorShape::Underscore => {
374+
let char_width = editor_font_size * 0.6;
375+
let line_y = painter_rect.bottom() - 1.0;
376+
ui.painter().line_segment(
377+
[egui::pos2(painter_rect.left(), line_y), egui::pos2(painter_rect.left() + char_width, line_y)],
378+
egui::Stroke::new(2.0, color),
379+
);
380+
}
381+
}
382+
}
383+
}
384+
}
385+
328386
// Keep focus when interaction happens within the editor (e.g. scrollbars)
329387
// but not on other focusable widgets.
330388
if output.response.lost_focus() && ui.memory(|mem| mem.focused().is_none()) {

src/ui_panels.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,18 +404,22 @@ impl EditorApp {
404404
.show_ui(ui, |ui| {
405405
if ui.selectable_value(&mut self.settings.cursor_shape, crate::settings::CursorShape::Bar, "Bar").changed() {
406406
let _ = self.settings.save();
407+
self.style_dirty = true;
407408
}
408409
if ui.selectable_value(&mut self.settings.cursor_shape, crate::settings::CursorShape::Block, "Block").changed() {
409410
let _ = self.settings.save();
411+
self.style_dirty = true;
410412
}
411413
if ui.selectable_value(&mut self.settings.cursor_shape, crate::settings::CursorShape::Underscore, "Underscore").changed() {
412414
let _ = self.settings.save();
415+
self.style_dirty = true;
413416
}
414417
});
415418
});
416419

417420
if ui.checkbox(&mut self.settings.cursor_blink, "Cursor blinking").changed() {
418421
let _ = self.settings.save();
422+
self.style_dirty = true;
419423
}
420424
// Max lines
421425
ui.horizontal(|ui| {

0 commit comments

Comments
 (0)