ced/src/ui/central_panel/editor.rs

223 lines
8.0 KiB
Rust
Raw Normal View History

2025-07-05 14:42:45 -04:00
use crate::app::TextEditor;
use eframe::egui;
use super::find_highlight::draw_find_highlight;
pub(super) fn editor_view(
ui: &mut egui::Ui,
app: &mut TextEditor,
) -> (egui::Response, Option<egui::Rect>) {
let current_match_position = app.get_current_match_position();
let show_find = app.show_find;
let prev_show_find = app.prev_show_find;
let show_preferences = app.show_preferences;
let show_about = app.show_about;
let show_shortcuts = app.show_shortcuts;
let word_wrap = app.word_wrap;
let font_size = app.font_size;
// Check if reset zoom was requested in previous frame
let reset_zoom_key = egui::Id::new("editor_reset_zoom");
let should_reset_zoom = ui.ctx().memory_mut(|mem| {
mem.data.get_temp::<bool>(reset_zoom_key).unwrap_or(false)
});
// Reset zoom if requested
if should_reset_zoom {
app.zoom_factor = 1.0;
ui.ctx().set_zoom_factor(1.0);
ui.ctx().memory_mut(|mem| {
mem.data.insert_temp(reset_zoom_key, false);
});
}
if let Some(active_tab) = app.get_active_tab_mut() {
let bg_color = ui.visuals().extreme_bg_color;
let editor_rect = ui.available_rect_before_wrap();
ui.painter().rect_filled(editor_rect, 0.0, bg_color);
let desired_width = if word_wrap {
ui.available_width()
} else {
f32::INFINITY
};
let text_edit = egui::TextEdit::multiline(&mut active_tab.content)
.frame(false)
.font(egui::TextStyle::Monospace)
.code_editor()
.desired_width(desired_width)
.desired_rows(0)
.lock_focus(true)
.cursor_at_end(false)
.id(egui::Id::new("main_text_editor"));
let output = text_edit.show(ui);
// Store text length for context menu
let text_len = active_tab.content.len();
// Right-click context menu
output.response.context_menu(|ui| {
if ui.button("Cut").clicked() {
ui.ctx().input_mut(|i| i.events.push(egui::Event::Cut));
ui.close_menu();
}
if ui.button("Copy").clicked() {
ui.ctx().input_mut(|i| i.events.push(egui::Event::Copy));
ui.close_menu();
}
if ui.button("Paste").clicked() {
ui.ctx()
.send_viewport_cmd(egui::ViewportCommand::RequestPaste);
ui.close_menu();
}
if ui.button("Delete").clicked() {
ui.ctx().input_mut(|i| {
i.events.push(egui::Event::Key {
key: egui::Key::Delete,
physical_key: None,
pressed: true,
repeat: false,
modifiers: egui::Modifiers::NONE,
})
});
ui.close_menu();
}
if ui.button("Select All").clicked() {
let text_edit_id = egui::Id::new("main_text_editor");
if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) {
let select_all_range = egui::text::CCursorRange::two(
egui::text::CCursor::new(0),
egui::text::CCursor::new(text_len),
);
state.cursor.set_char_range(Some(select_all_range));
egui::TextEdit::store_state(ui.ctx(), text_edit_id, state);
}
ui.close_menu();
}
ui.separator();
if ui.button("Reset Zoom").clicked() {
ui.ctx().memory_mut(|mem| {
mem.data.insert_temp(reset_zoom_key, true);
});
ui.close_menu();
}
});
let cursor_rect = if let Some(cursor_range) = output.state.cursor.char_range() {
let cursor_pos = cursor_range.primary.index;
let content = &active_tab.content;
let text_up_to_cursor = &content[..cursor_pos.min(content.len())];
let cursor_line = text_up_to_cursor.chars().filter(|&c| c == '\n').count();
let font_id = ui
.style()
.text_styles
.get(&egui::TextStyle::Monospace)
.unwrap_or(&egui::FontId::monospace(font_size))
.clone();
let line_height = ui.fonts(|fonts| fonts.row_height(&font_id));
let y_pos = output.response.rect.top() + (cursor_line as f32 * line_height);
let cursor_rect = egui::Rect::from_min_size(
egui::pos2(output.response.rect.left(), y_pos),
egui::vec2(2.0, line_height),
);
Some(cursor_rect)
} else {
None
};
if !show_find && prev_show_find {
if let Some((start_pos, end_pos)) = current_match_position {
let text_edit_id = egui::Id::new("main_text_editor");
if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) {
let cursor_range = egui::text::CCursorRange::two(
egui::text::CCursor::new(start_pos),
egui::text::CCursor::new(end_pos),
);
state.cursor.set_char_range(Some(cursor_range));
egui::TextEdit::store_state(ui.ctx(), text_edit_id, state);
}
}
}
if show_find {
if let Some((start_pos, end_pos)) = current_match_position {
draw_find_highlight(
ui,
&active_tab.content,
start_pos,
end_pos,
output.response.rect,
font_size,
);
}
}
if output.response.changed() {
active_tab.update_modified_state();
app.find_matches.clear();
app.current_match_index = None;
}
if !output.response.has_focus()
&& !show_preferences
&& !show_about
&& !show_shortcuts
&& !show_find
{
output.response.request_focus();
}
(output.response, cursor_rect)
} else {
(ui.label("No file open, how did you get here?"), None)
}
}
pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) {
let word_wrap = app.word_wrap;
if word_wrap {
let (_response, _cursor_rect) = editor_view(ui, app);
} else {
let estimated_width = app.calculate_content_based_width(ui);
let output = egui::ScrollArea::horizontal()
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.allocate_ui_with_layout(
egui::Vec2::new(estimated_width, ui.available_height()),
egui::Layout::left_to_right(egui::Align::TOP),
|ui| editor_view(ui, app),
)
});
let editor_response = &output.inner.inner.0;
if let Some(cursor_rect) = output.inner.inner.1 {
let text_edit_id = egui::Id::new("main_text_editor");
let current_cursor_pos =
if let Some(state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) {
state.cursor.char_range().map(|range| range.primary.index)
} else {
None
};
let cursor_moved = current_cursor_pos != app.previous_cursor_position;
let text_changed = editor_response.changed();
let should_scroll = (cursor_moved || text_changed)
&& {
let visible_area = ui.clip_rect();
!visible_area.intersects(cursor_rect)
};
if should_scroll {
ui.scroll_to_rect(cursor_rect, Some(egui::Align::Center));
}
app.previous_cursor_position = current_cursor_pos;
}
}
}