Implement find and improvements for focus
This commit is contained in:
parent
81a0bbc094
commit
f3a8fef8f8
10 changed files with 358 additions and 105 deletions
32
Cargo.lock
generated
32
Cargo.lock
generated
|
|
@ -998,7 +998,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-config"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"atomicwrites",
|
||||
"cosmic-config-derive",
|
||||
|
|
@ -1013,7 +1013,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-config-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
|
|
@ -1083,7 +1083,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-theme"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"almost",
|
||||
"cosmic-config",
|
||||
|
|
@ -2612,7 +2612,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"iced_accessibility",
|
||||
"iced_core",
|
||||
|
|
@ -2627,7 +2627,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_accessibility"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_winit",
|
||||
|
|
@ -2636,7 +2636,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_core"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"instant",
|
||||
|
|
@ -2652,7 +2652,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_futures"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"iced_core",
|
||||
|
|
@ -2665,7 +2665,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_graphics"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bytemuck",
|
||||
|
|
@ -2688,7 +2688,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_renderer"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"iced_graphics",
|
||||
"iced_tiny_skia",
|
||||
|
|
@ -2701,7 +2701,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_runtime"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"iced_core",
|
||||
"iced_futures",
|
||||
|
|
@ -2711,7 +2711,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_style"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"iced_core",
|
||||
"once_cell",
|
||||
|
|
@ -2721,7 +2721,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_tiny_skia"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cosmic-text",
|
||||
|
|
@ -2739,7 +2739,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_wgpu"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bytemuck",
|
||||
|
|
@ -2759,7 +2759,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_widget"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"iced_renderer",
|
||||
"iced_runtime",
|
||||
|
|
@ -2773,7 +2773,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_winit"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"iced_graphics",
|
||||
"iced_runtime",
|
||||
|
|
@ -3092,7 +3092,7 @@ checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
|||
[[package]]
|
||||
name = "libcosmic"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#5b2ac941c36e1b7e2b4fa9a9162b64dc10afffef"
|
||||
source = "git+https://github.com/pop-os/libcosmic#68c760e65203c5124844c2083224d8c83010ed1e"
|
||||
dependencies = [
|
||||
"apply",
|
||||
"ashpd",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ default-font-size = Default font size
|
|||
keyboard-shortcuts = Keyboard shortcuts
|
||||
enable-vim-bindings = Enable Vim bindings
|
||||
|
||||
# Find
|
||||
find-placeholder = Find...
|
||||
|
||||
# Menu
|
||||
|
||||
## File
|
||||
|
|
|
|||
3
res/icons/edit-clear-symbolic.svg
Normal file
3
res/icons/edit-clear-symbolic.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 2.00305L0 8.00305L5 14.0031H16V2.00305H5ZM7 5.00305H8C8.277 5.00305 8.526 5.11505 8.707 5.29605L10 6.58905L11.293 5.29605C11.3856 5.20288 11.4958 5.129 11.6171 5.0787C11.7385 5.02841 11.8686 5.0027 12 5.00305H13V6.00305C13.0002 6.1344 12.9744 6.26448 12.9241 6.38582C12.8738 6.50715 12.8 6.61735 12.707 6.71005L11.414 8.00305L12.707 9.29605C12.887 9.47605 13 9.72605 13 10.0031V11.0031H12C11.8687 11.0032 11.7386 10.9775 11.6172 10.9272C11.4959 10.8769 11.3857 10.8031 11.293 10.7101L10 9.41705L8.707 10.7101C8.6143 10.8031 8.50409 10.8769 8.38275 10.9272C8.26141 10.9775 8.13134 11.0032 8 11.0031H7V10.0031C7 9.72605 7.112 9.47605 7.293 9.29605L8.586 8.00305L7.293 6.71005C7.19996 6.61735 7.12618 6.50715 7.0759 6.38582C7.02561 6.26448 6.99981 6.1344 7 6.00305V5.00305Z" fill="#232323"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 904 B |
3
res/icons/go-up-symbolic.svg
Normal file
3
res/icons/go-up-symbolic.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.44039 10.063L7.50339 4L13.5654 10.063C15.0034 11.5 13.5034 12.75 12.4404 11.688L7.50339 6.75L2.56539 11.688C1.75339 12.5 0.253389 11.25 1.44039 10.063Z" fill="#232323"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 285 B |
|
|
@ -19,6 +19,8 @@ pub enum Action {
|
|||
CloseProject,
|
||||
Copy,
|
||||
Cut,
|
||||
Find,
|
||||
FindAndReplace,
|
||||
NewFile,
|
||||
NewWindow,
|
||||
OpenFileDialog,
|
||||
|
|
@ -42,6 +44,8 @@ impl Action {
|
|||
Self::CloseProject => Message::CloseProject,
|
||||
Self::Copy => Message::Copy,
|
||||
Self::Cut => Message::Cut,
|
||||
Self::Find => Message::Find(Some(false)),
|
||||
Self::FindAndReplace => Message::Find(Some(true)),
|
||||
Self::NewFile => Message::NewFile,
|
||||
Self::NewWindow => Message::NewWindow,
|
||||
Self::OpenFileDialog => Message::OpenFileDialog,
|
||||
|
|
@ -111,6 +115,8 @@ impl KeyBind {
|
|||
bind!([Ctrl], W, CloseFile);
|
||||
bind!([Ctrl], X, Cut);
|
||||
bind!([Ctrl], C, Copy);
|
||||
bind!([Ctrl], F, Find);
|
||||
bind!([Ctrl], H, FindAndReplace);
|
||||
bind!([Ctrl], V, Paste);
|
||||
bind!([Ctrl], T, NewFile);
|
||||
bind!([Ctrl], N, NewWindow);
|
||||
|
|
|
|||
|
|
@ -30,9 +30,11 @@ impl IconCache {
|
|||
};
|
||||
}
|
||||
|
||||
bundle!("edit-clear-symbolic", 16);
|
||||
bundle!("folder-open-symbolic", 16);
|
||||
bundle!("go-down-symbolic", 16);
|
||||
bundle!("go-next-symbolic", 16);
|
||||
bundle!("go-up-symbolic", 16);
|
||||
bundle!("list-add-symbolic", 16);
|
||||
bundle!("object-select-symbolic", 16);
|
||||
bundle!("window-close-symbolic", 16);
|
||||
|
|
|
|||
161
src/main.rs
161
src/main.rs
|
|
@ -181,6 +181,11 @@ pub enum Message {
|
|||
Cut,
|
||||
DefaultFont(usize),
|
||||
DefaultFontSize(usize),
|
||||
Find(Option<bool>),
|
||||
FindNext,
|
||||
FindPrevious,
|
||||
FindReplaceValueChanged(String),
|
||||
FindSearchValueChanged(String),
|
||||
GitProjectStatus(Vec<(String, PathBuf, Vec<GitStatus>)>),
|
||||
Key(keyboard::Modifiers, keyboard::KeyCode),
|
||||
NewFile,
|
||||
|
|
@ -241,6 +246,13 @@ impl ContextPage {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Find {
|
||||
None,
|
||||
Find,
|
||||
FindAndReplace,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
core: Core,
|
||||
nav_model: segmented_button::SingleSelectModel,
|
||||
|
|
@ -253,6 +265,11 @@ pub struct App {
|
|||
font_sizes: Vec<u16>,
|
||||
theme_names: Vec<String>,
|
||||
context_page: ContextPage,
|
||||
text_box_id: widget::Id,
|
||||
find_opt: Option<bool>,
|
||||
find_replace_value: String,
|
||||
find_search_id: widget::Id,
|
||||
find_search_value: String,
|
||||
git_project_status: Option<Vec<(String, PathBuf, Vec<GitStatus>)>>,
|
||||
projects: Vec<(String, PathBuf)>,
|
||||
project_search_id: widget::Id,
|
||||
|
|
@ -440,6 +457,21 @@ impl App {
|
|||
self.update_config()
|
||||
}
|
||||
|
||||
fn update_focus(&self) -> Command<Message> {
|
||||
if self.core.window.show_context {
|
||||
match self.context_page {
|
||||
ContextPage::ProjectSearch => {
|
||||
widget::text_input::focus(self.project_search_id.clone())
|
||||
}
|
||||
_ => Command::none(),
|
||||
}
|
||||
} else if self.find_opt.is_some() {
|
||||
widget::text_input::focus(self.find_search_id.clone())
|
||||
} else {
|
||||
widget::text_input::focus(self.text_box_id.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn update_nav_bar_active(&mut self) {
|
||||
let tab_path_opt = match self.active_tab() {
|
||||
Some(Tab::Editor(tab)) => tab.path_opt.clone(),
|
||||
|
|
@ -504,7 +536,7 @@ impl App {
|
|||
|
||||
let window_title = format!("{title} - COSMIC Text Editor");
|
||||
self.set_header_title(title.clone());
|
||||
self.set_window_title(window_title)
|
||||
Command::batch([self.set_window_title(window_title), self.update_focus()])
|
||||
}
|
||||
|
||||
fn document_statistics(&self) -> Element<Message> {
|
||||
|
|
@ -942,6 +974,11 @@ impl Application for App {
|
|||
font_sizes,
|
||||
theme_names,
|
||||
context_page: ContextPage::Settings,
|
||||
text_box_id: widget::Id::unique(),
|
||||
find_opt: None,
|
||||
find_replace_value: String::new(),
|
||||
find_search_id: widget::Id::unique(),
|
||||
find_search_value: String::new(),
|
||||
git_project_status: None,
|
||||
projects: Vec::new(),
|
||||
project_search_id: widget::Id::unique(),
|
||||
|
|
@ -1021,6 +1058,25 @@ impl Application for App {
|
|||
Some(&self.nav_model)
|
||||
}
|
||||
|
||||
fn on_context_drawer(&mut self) -> Command<Message> {
|
||||
// Focus correct widget
|
||||
self.update_focus()
|
||||
}
|
||||
|
||||
//TODO: currently the first escape unfocuses, and the second calls this function
|
||||
fn on_escape(&mut self) -> Command<Message> {
|
||||
if self.core.window.show_context {
|
||||
// Close context drawer if open
|
||||
self.core.window.show_context = false;
|
||||
} else if self.find_opt.is_some() {
|
||||
// Close find if open
|
||||
self.find_opt = None;
|
||||
}
|
||||
|
||||
// Focus correct widget
|
||||
self.update_focus()
|
||||
}
|
||||
|
||||
fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<Message> {
|
||||
// Toggle open state and get clone of node data
|
||||
let node_opt = match self.nav_model.data_mut::<ProjectNode>(id) {
|
||||
|
|
@ -1160,6 +1216,38 @@ impl Application for App {
|
|||
log::warn!("failed to find font with index {}", index);
|
||||
}
|
||||
},
|
||||
Message::Find(find_opt) => {
|
||||
self.find_opt = find_opt;
|
||||
|
||||
// Focus correct input
|
||||
return self.update_focus();
|
||||
}
|
||||
Message::FindNext => {
|
||||
if !self.find_search_value.is_empty() {
|
||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
||||
tab.search(&self.find_search_value, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Focus correct input
|
||||
return self.update_focus();
|
||||
}
|
||||
Message::FindPrevious => {
|
||||
if !self.find_search_value.is_empty() {
|
||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
||||
tab.search(&self.find_search_value, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Focus correct input
|
||||
return self.update_focus();
|
||||
}
|
||||
Message::FindReplaceValueChanged(value) => {
|
||||
self.find_replace_value = value;
|
||||
}
|
||||
Message::FindSearchValueChanged(value) => {
|
||||
self.find_search_value = value;
|
||||
}
|
||||
Message::GitProjectStatus(project_status) => {
|
||||
self.git_project_status = Some(project_status);
|
||||
}
|
||||
|
|
@ -1384,8 +1472,8 @@ impl Application for App {
|
|||
Message::ProjectSearchResult(project_search_result) => {
|
||||
self.project_search_result = Some(project_search_result);
|
||||
|
||||
// Ensure input remains focused
|
||||
return widget::text_input::focus(self.project_search_id.clone());
|
||||
// Focus correct input
|
||||
return self.update_focus();
|
||||
}
|
||||
Message::ProjectSearchSubmit => {
|
||||
//TODO: Figure out length requirements?
|
||||
|
|
@ -1595,13 +1683,12 @@ impl Application for App {
|
|||
|x| x,
|
||||
);
|
||||
}
|
||||
ContextPage::ProjectSearch => {
|
||||
// Ensure focus of correct input
|
||||
return widget::text_input::focus(self.project_search_id.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure focus of correct input
|
||||
return self.update_focus();
|
||||
}
|
||||
Message::ToggleLineNumbers => {
|
||||
self.config.line_numbers = !self.config.line_numbers;
|
||||
|
|
@ -1713,23 +1800,22 @@ impl Application for App {
|
|||
}
|
||||
};
|
||||
let mut text_box = text_box(&tab.editor, self.config.metrics())
|
||||
.id(self.text_box_id.clone())
|
||||
.on_changed(Message::TabChanged(tab_id))
|
||||
.has_context_menu(tab.context_menu.is_some())
|
||||
.on_context_menu(move |position_opt| {
|
||||
Message::TabContextMenu(tab_id, position_opt)
|
||||
});
|
||||
if self.config.line_numbers {
|
||||
text_box = text_box.line_numbers();
|
||||
}
|
||||
let tab_element: Element<'_, Message> = match tab.context_menu {
|
||||
Some(position) => widget::popover(
|
||||
text_box.context_menu(position),
|
||||
menu::context_menu(&self.config, tab_id),
|
||||
)
|
||||
.position(position)
|
||||
.into(),
|
||||
None => text_box.into(),
|
||||
let mut popover =
|
||||
widget::popover(text_box, menu::context_menu(&self.config, tab_id));
|
||||
popover = match tab.context_menu {
|
||||
Some(position) => popover.position(position),
|
||||
None => popover.show_popup(false),
|
||||
};
|
||||
tab_column = tab_column.push(tab_element);
|
||||
tab_column = tab_column.push(popover);
|
||||
tab_column = tab_column.push(text(status).font(Font::MONOSPACE));
|
||||
}
|
||||
Some(Tab::GitDiff(tab)) => {
|
||||
|
|
@ -1789,6 +1875,49 @@ impl Application for App {
|
|||
None => {}
|
||||
}
|
||||
|
||||
if let Some(replace) = &self.find_opt {
|
||||
let text_input =
|
||||
widget::text_input::text_input(fl!("find-placeholder"), &self.find_search_value)
|
||||
.id(self.find_search_id.clone())
|
||||
.on_input(Message::FindSearchValueChanged)
|
||||
//TODO: shift+enter for FindPrevious
|
||||
.on_submit(Message::FindNext)
|
||||
.width(Length::Fixed(320.0))
|
||||
.trailing_icon(
|
||||
button(icon_cache_get("edit-clear-symbolic", 16))
|
||||
.on_press(Message::FindSearchValueChanged(String::new()))
|
||||
.style(style::Button::Icon)
|
||||
.into(),
|
||||
);
|
||||
let find_widget = widget::row::with_children(vec![
|
||||
text_input.into(),
|
||||
button(icon_cache_get("go-up-symbolic", 16))
|
||||
.on_press(Message::FindPrevious)
|
||||
.padding(space_xxs)
|
||||
.style(style::Button::Icon)
|
||||
.into(),
|
||||
button(icon_cache_get("go-down-symbolic", 16))
|
||||
.on_press(Message::FindNext)
|
||||
.padding(space_xxs)
|
||||
.style(style::Button::Icon)
|
||||
.into(),
|
||||
widget::horizontal_space(Length::Fill).into(),
|
||||
button(icon_cache_get("window-close-symbolic", 16))
|
||||
.on_press(Message::Find(None))
|
||||
.padding(space_xxs)
|
||||
.style(style::Button::Icon)
|
||||
.into(),
|
||||
])
|
||||
.align_items(Alignment::Center)
|
||||
.padding(space_xxs)
|
||||
.spacing(space_xxs);
|
||||
|
||||
tab_column = tab_column.push(
|
||||
widget::cosmic_container::container(find_widget)
|
||||
.layer(cosmic_theme::Layer::Primary),
|
||||
);
|
||||
}
|
||||
|
||||
let content: Element<_> = tab_column.into();
|
||||
|
||||
// Uncomment to debug layout:
|
||||
|
|
|
|||
|
|
@ -197,8 +197,8 @@ pub fn menu_bar<'a>(config: &Config) -> Element<'a, Message> {
|
|||
menu_item(fl!("paste"), Message::Paste),
|
||||
menu_item(fl!("select-all"), Message::SelectAll),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_key(fl!("find"), "Ctrl + F", Message::Todo),
|
||||
menu_key(fl!("replace"), "Ctrl + H", Message::Todo),
|
||||
menu_item(fl!("find"), Message::Find(Some(false))),
|
||||
menu_item(fl!("replace"), Message::Find(Some(true))),
|
||||
menu_item(
|
||||
fl!("find-in-project"),
|
||||
Message::ToggleContextPage(ContextPage::ProjectSearch),
|
||||
|
|
|
|||
54
src/tab.rs
54
src/tab.rs
|
|
@ -199,4 +199,58 @@ impl EditorTab {
|
|||
fl!("new-document")
|
||||
}
|
||||
}
|
||||
|
||||
// Code adapted from cosmic-text ViEditor search
|
||||
pub fn search(&self, value: &str, forwards: bool) -> bool {
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
let mut cursor = editor.cursor();
|
||||
let start_line = cursor.line;
|
||||
if forwards {
|
||||
while cursor.line < editor.with_buffer(|buffer| buffer.lines.len()) {
|
||||
if let Some(index) = editor.with_buffer(|buffer| {
|
||||
buffer.lines[cursor.line]
|
||||
.text()
|
||||
.match_indices(value)
|
||||
.filter_map(|(i, _)| {
|
||||
if cursor.line != start_line || i > cursor.index {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next()
|
||||
}) {
|
||||
cursor.index = index;
|
||||
editor.set_cursor(cursor);
|
||||
return true;
|
||||
}
|
||||
|
||||
cursor.line += 1;
|
||||
}
|
||||
} else {
|
||||
cursor.line += 1;
|
||||
while cursor.line > 0 {
|
||||
cursor.line -= 1;
|
||||
|
||||
if let Some(index) = editor.with_buffer(|buffer| {
|
||||
buffer.lines[cursor.line]
|
||||
.text()
|
||||
.rmatch_indices(value)
|
||||
.filter_map(|(i, _)| {
|
||||
if cursor.line != start_line || i < cursor.index {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next()
|
||||
}) {
|
||||
cursor.index = index;
|
||||
editor.set_cursor(cursor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
195
src/text_box.rs
195
src/text_box.rs
|
|
@ -12,7 +12,11 @@ use cosmic::{
|
|||
image,
|
||||
layout::{self, Layout},
|
||||
renderer::{self, Quad},
|
||||
widget::{self, tree, Widget},
|
||||
widget::{
|
||||
self,
|
||||
operation::{self, Operation, OperationOutputWrapper},
|
||||
tree, Id, Widget,
|
||||
},
|
||||
Shell,
|
||||
},
|
||||
};
|
||||
|
|
@ -29,10 +33,11 @@ use crate::{line_number::LineNumberKey, FONT_SYSTEM, LINE_NUMBER_CACHE, SWASH_CA
|
|||
pub struct TextBox<'a, Message> {
|
||||
editor: &'a Mutex<ViEditor<'static, 'static>>,
|
||||
metrics: Metrics,
|
||||
id: Option<Id>,
|
||||
padding: Padding,
|
||||
on_changed: Option<Message>,
|
||||
click_timing: Duration,
|
||||
context_menu: Option<Point>,
|
||||
has_context_menu: bool,
|
||||
on_context_menu: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
|
||||
line_numbers: bool,
|
||||
}
|
||||
|
|
@ -45,15 +50,21 @@ where
|
|||
Self {
|
||||
editor,
|
||||
metrics,
|
||||
id: None,
|
||||
padding: Padding::new(0.0),
|
||||
on_changed: None,
|
||||
click_timing: Duration::from_millis(500),
|
||||
context_menu: None,
|
||||
has_context_menu: false,
|
||||
on_context_menu: None,
|
||||
line_numbers: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = Some(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
|
||||
self.padding = padding.into();
|
||||
self
|
||||
|
|
@ -69,8 +80,8 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
pub fn context_menu(mut self, position: Point) -> Self {
|
||||
self.context_menu = Some(position);
|
||||
pub fn has_context_menu(mut self, has_context_menu: bool) -> Self {
|
||||
self.has_context_menu = has_context_menu;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -252,6 +263,18 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut widget::Tree,
|
||||
_layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
operation.focusable(state, self.id.as_ref());
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &widget::Tree,
|
||||
|
|
@ -611,79 +634,92 @@ where
|
|||
Event::Keyboard(KeyEvent::KeyPressed {
|
||||
key_code,
|
||||
modifiers,
|
||||
}) => match key_code {
|
||||
KeyCode::Left => {
|
||||
editor.action(Action::Motion(Motion::Left));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Right => {
|
||||
editor.action(Action::Motion(Motion::Right));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Up => {
|
||||
editor.action(Action::Motion(Motion::Up));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Down => {
|
||||
editor.action(Action::Motion(Motion::Down));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Home => {
|
||||
editor.action(Action::Motion(Motion::Home));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::End => {
|
||||
editor.action(Action::Motion(Motion::End));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::PageUp => {
|
||||
editor.action(Action::Motion(Motion::PageUp));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::PageDown => {
|
||||
editor.action(Action::Motion(Motion::PageDown));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Escape => {
|
||||
editor.action(Action::Escape);
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
editor.action(Action::Enter);
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
editor.action(Action::Backspace);
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Delete => {
|
||||
editor.action(Action::Delete);
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Tab => {
|
||||
if modifiers.shift() {
|
||||
editor.action(Action::Unindent);
|
||||
} else {
|
||||
editor.action(Action::Indent);
|
||||
}) => {
|
||||
// Only parse keys when focused
|
||||
if state.is_focused {
|
||||
match key_code {
|
||||
KeyCode::Left => {
|
||||
editor.action(Action::Motion(Motion::Left));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Right => {
|
||||
editor.action(Action::Motion(Motion::Right));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Up => {
|
||||
editor.action(Action::Motion(Motion::Up));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Down => {
|
||||
editor.action(Action::Motion(Motion::Down));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Home => {
|
||||
editor.action(Action::Motion(Motion::Home));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::End => {
|
||||
editor.action(Action::Motion(Motion::End));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::PageUp => {
|
||||
editor.action(Action::Motion(Motion::PageUp));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::PageDown => {
|
||||
editor.action(Action::Motion(Motion::PageDown));
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Escape => {
|
||||
editor.action(Action::Escape);
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
editor.action(Action::Enter);
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
editor.action(Action::Backspace);
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Delete => {
|
||||
editor.action(Action::Delete);
|
||||
status = Status::Captured;
|
||||
}
|
||||
KeyCode::Tab => {
|
||||
if modifiers.shift() {
|
||||
editor.action(Action::Unindent);
|
||||
} else {
|
||||
editor.action(Action::Indent);
|
||||
}
|
||||
status = Status::Captured;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
status = Status::Captured;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
|
||||
state.modifiers = modifiers;
|
||||
}
|
||||
Event::Keyboard(KeyEvent::CharacterReceived(character)) => {
|
||||
// Only parse keys when Super, Ctrl, and Alt are not pressed
|
||||
if !state.modifiers.logo() && !state.modifiers.control() && !state.modifiers.alt() {
|
||||
if !character.is_control() {
|
||||
editor.action(Action::Insert(character));
|
||||
// Only parse keys when focused
|
||||
if state.is_focused {
|
||||
// Only parse keys when Super, Ctrl, and Alt are not pressed
|
||||
if !state.modifiers.logo()
|
||||
&& !state.modifiers.control()
|
||||
&& !state.modifiers.alt()
|
||||
{
|
||||
if !character.is_control() {
|
||||
editor.action(Action::Insert(character));
|
||||
}
|
||||
status = Status::Captured;
|
||||
}
|
||||
status = Status::Captured;
|
||||
}
|
||||
}
|
||||
Event::Mouse(MouseEvent::ButtonPressed(button)) => {
|
||||
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
||||
state.is_focused = true;
|
||||
|
||||
// Handle left click drag
|
||||
if let Button::Left = button {
|
||||
let x_logical = p.x - self.padding.left;
|
||||
|
|
@ -746,12 +782,13 @@ where
|
|||
|
||||
// Update context menu state
|
||||
if let Some(on_context_menu) = &self.on_context_menu {
|
||||
shell.publish((on_context_menu)(match self.context_menu {
|
||||
Some(_) => None,
|
||||
None => match button {
|
||||
shell.publish((on_context_menu)(if self.has_context_menu {
|
||||
None
|
||||
} else {
|
||||
match button {
|
||||
Button::Right => Some(p),
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -869,6 +906,7 @@ pub struct State {
|
|||
click: Option<(ClickKind, Instant)>,
|
||||
dragging: Option<Dragging>,
|
||||
editor_offset_x: Cell<i32>,
|
||||
is_focused: bool,
|
||||
scale_factor: Cell<f32>,
|
||||
scroll_pixels: f32,
|
||||
scrollbar_rect: Cell<Rectangle<f32>>,
|
||||
|
|
@ -883,6 +921,7 @@ impl State {
|
|||
click: None,
|
||||
dragging: None,
|
||||
editor_offset_x: Cell::new(0),
|
||||
is_focused: false,
|
||||
scale_factor: Cell::new(1.0),
|
||||
scroll_pixels: 0.0,
|
||||
scrollbar_rect: Cell::new(Rectangle::default()),
|
||||
|
|
@ -890,3 +929,17 @@ impl State {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl operation::Focusable for State {
|
||||
fn is_focused(&self) -> bool {
|
||||
self.is_focused
|
||||
}
|
||||
|
||||
fn focus(&mut self) {
|
||||
self.is_focused = true;
|
||||
}
|
||||
|
||||
fn unfocus(&mut self) {
|
||||
self.is_focused = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue