Add find stub

This commit is contained in:
Jeremy Soller 2024-01-11 13:20:16 -07:00
parent 8766819ea8
commit 928269a239
No known key found for this signature in database
GPG key ID: DCFCA852D3906975
9 changed files with 266 additions and 17 deletions

49
src/icon_cache.rs Normal file
View file

@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::widget::icon;
use std::collections::HashMap;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct IconCacheKey {
name: &'static str,
size: u16,
}
pub struct IconCache {
cache: HashMap<IconCacheKey, icon::Handle>,
}
impl IconCache {
pub fn new() -> Self {
let mut cache = HashMap::new();
macro_rules! bundle {
($name:expr, $size:expr) => {
let data: &'static [u8] = include_bytes!(concat!("../res/icons/", $name, ".svg"));
cache.insert(
IconCacheKey {
name: $name,
size: $size,
},
icon::from_svg_bytes(data).symbolic(true),
);
};
}
bundle!("edit-clear-symbolic", 16);
bundle!("go-down-symbolic", 16);
bundle!("go-up-symbolic", 16);
bundle!("window-close-symbolic", 16);
Self { cache }
}
pub fn get(&mut self, name: &'static str, size: u16) -> icon::Icon {
let handle = self
.cache
.entry(IconCacheKey { name, size })
.or_insert_with(|| icon::from_name(name).size(size).handle())
.clone();
icon::icon(handle).size(size)
}
}

View file

@ -14,10 +14,10 @@ use cosmic::{
futures::SinkExt,
keyboard::{Event as KeyEvent, KeyCode, Modifiers},
subscription::{self, Subscription},
window, Event, Length, Padding, Point,
window, Alignment, Event, Length, Padding, Point,
},
style,
widget::{self, segmented_button},
widget::{self, button, segmented_button},
Application, ApplicationExt, Element,
};
use cosmic_text::{fontdb::FaceInfo, Family, Stretch, Weight};
@ -33,6 +33,9 @@ use tokio::sync::mpsc;
use config::{AppTheme, Config, CONFIG_VERSION};
mod config;
use icon_cache::IconCache;
mod icon_cache;
mod localize;
use menu::menu_bar;
@ -46,6 +49,15 @@ mod terminal_box;
mod terminal_theme;
lazy_static::lazy_static! {
static ref ICON_CACHE: Mutex<IconCache> = Mutex::new(IconCache::new());
}
pub fn icon_cache_get(name: &'static str, size: u16) -> widget::icon::Icon {
let mut icon_cache = ICON_CACHE.lock().unwrap();
icon_cache.get(name, size)
}
/// Runs application with these settings
#[rustfmt::skip]
fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -150,6 +162,11 @@ pub enum Message {
DefaultFontWeight(usize),
DefaultBoldFontWeight(usize),
DefaultZoomStep(usize),
Find(bool),
FindNext,
FindPrevious,
FindSearchValueChanged(String),
Modifiers(Modifiers),
Paste(Option<segmented_button::Entity>),
PasteValue(Option<segmented_button::Entity>, String),
SelectAll(Option<segmented_button::Entity>),
@ -210,9 +227,14 @@ pub struct App {
theme_names: Vec<String>,
themes: HashMap<String, TermColors>,
context_page: ContextPage,
terminal_id: widget::Id,
find: bool,
find_search_id: widget::Id,
find_search_value: String,
term_event_tx_opt: Option<mpsc::Sender<(segmented_button::Entity, TermEvent)>>,
term_config: TermConfig,
show_advanced_font_settings: bool,
modifiers: Modifiers,
}
impl App {
@ -243,6 +265,17 @@ impl App {
self.update_config()
}
fn update_focus(&self) -> Command<Message> {
if self.core.window.show_context {
Command::none()
} else if self.find {
widget::text_input::focus(self.find_search_id.clone())
} else {
widget::text_input::focus(self.terminal_id.clone())
}
}
// Call this any time the tab changes
fn update_title(&mut self) -> Command<Message> {
let (header_title, window_title) = match self.tab_model.text(self.tab_model.active()) {
Some(tab_title) => (
@ -252,7 +285,7 @@ impl App {
None => (String::new(), "COSMIC Terminal".to_string()),
};
self.set_header_title(header_title);
self.set_window_title(window_title)
Command::batch([self.set_window_title(window_title), self.update_focus()])
}
fn set_curr_font_weights_and_stretches(&mut self) {
@ -631,9 +664,14 @@ impl Application for App {
theme_names,
themes,
context_page: ContextPage::Settings,
terminal_id: widget::Id::unique(),
find: false,
find_search_id: widget::Id::unique(),
find_search_value: String::new(),
term_config: flags.term_config,
term_event_tx_opt: None,
show_advanced_font_settings: false,
modifiers: Modifiers::empty(),
};
app.set_curr_font_weights_and_stretches();
@ -642,6 +680,20 @@ impl Application for App {
(app, command)
}
//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 {
// Close find if open
self.find = false;
}
// Focus correct widget
self.update_focus()
}
/// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
@ -746,6 +798,34 @@ impl Application for App {
log::warn!("failed to find zoom step with index {}", index);
}
},
Message::Find(find) => {
self.find = find;
// Focus correct input
return self.update_focus();
}
Message::FindNext => {
if !self.find_search_value.is_empty() {
//TODO
}
// Focus correct input
return self.update_focus();
}
Message::FindPrevious => {
if !self.find_search_value.is_empty() {
//TODO
}
// Focus correct input
return self.update_focus();
}
Message::FindSearchValueChanged(value) => {
self.find_search_value = value;
}
Message::Modifiers(modifiers) => {
self.modifiers = modifiers;
}
Message::Paste(entity_opt) => {
return clipboard::read(move |value_opt| match value_opt {
Some(value) => message::app(Message::PasteValue(entity_opt, value)),
@ -1009,9 +1089,11 @@ impl Application for App {
let entity = self.tab_model.active();
match self.tab_model.data::<Mutex<Terminal>>(entity) {
Some(terminal) => {
let terminal_box = terminal_box(terminal).on_context_menu(move |position_opt| {
Message::TabContextMenu(entity, position_opt)
});
let terminal_box = terminal_box(terminal)
.id(self.terminal_id.clone())
.on_context_menu(move |position_opt| {
Message::TabContextMenu(entity, position_opt)
});
let context_menu = {
let terminal = terminal.lock().unwrap();
@ -1034,6 +1116,60 @@ impl Application for App {
}
}
if self.find {
let find_input =
widget::text_input::text_input(fl!("find-placeholder"), &self.find_search_value)
.id(self.find_search_id.clone())
.on_input(Message::FindSearchValueChanged)
.on_submit(if self.modifiers.contains(Modifiers::SHIFT) {
Message::FindPrevious
} else {
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![
find_input.into(),
widget::tooltip(
button(icon_cache_get("go-up-symbolic", 16))
.on_press(Message::FindPrevious)
.padding(space_xxs)
.style(style::Button::Icon),
fl!("find-previous"),
widget::tooltip::Position::Top,
)
.into(),
widget::tooltip(
button(icon_cache_get("go-down-symbolic", 16))
.on_press(Message::FindNext)
.padding(space_xxs)
.style(style::Button::Icon),
fl!("find-next"),
widget::tooltip::Position::Top,
)
.into(),
widget::horizontal_space(Length::Fill).into(),
button(icon_cache_get("window-close-symbolic", 16))
.on_press(Message::Find(false))
.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:
@ -1068,6 +1204,16 @@ impl Application for App {
None
}
}
Event::Keyboard(KeyEvent::KeyPressed {
key_code: KeyCode::F,
modifiers,
}) => {
if modifiers == Modifiers::CTRL | Modifiers::SHIFT {
Some(Message::Find(true))
} else {
None
}
}
Event::Keyboard(KeyEvent::KeyPressed {
key_code: KeyCode::T,
modifiers,
@ -1118,6 +1264,9 @@ impl Application for App {
None
}
}
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
Some(Message::Modifiers(modifiers))
}
_ => None,
}),
subscription::channel(

View file

@ -105,13 +105,6 @@ pub fn menu_bar<'a>() -> Element<'a, Message> {
)
};
let menu_key = |label, key, message| {
MenuTree::new(
menu_button!(widget::text(label), horizontal_space(Length::Fill), key)
.on_press(message),
)
};
MenuBar::new(vec![
MenuTree::with_children(
menu_root(fl!("file")),
@ -131,7 +124,7 @@ pub fn menu_bar<'a>() -> Element<'a, Message> {
menu_item(fl!("paste"), Message::Paste(None)),
menu_item(fl!("select-all"), Message::SelectAll(None)),
MenuTree::new(horizontal_rule(1)),
menu_key(fl!("find"), "Ctrl + F", Message::Todo("find")),
menu_item(fl!("find"), Message::Find(true)),
],
),
MenuTree::with_children(

View file

@ -19,7 +19,11 @@ use cosmic::{
layout::{self, Layout},
renderer::{self, Quad, Renderer as _},
text::Renderer as _,
widget::{self, tree, Widget},
widget::{
self,
operation::{self, Operation, OperationOutputWrapper},
tree, Id, Widget,
},
Shell,
},
theme::Theme,
@ -37,6 +41,7 @@ use crate::{Terminal, TerminalScroll};
pub struct TerminalBox<'a, Message> {
terminal: &'a Mutex<Terminal>,
id: Option<Id>,
padding: Padding,
click_timing: Duration,
context_menu: Option<Point>,
@ -50,6 +55,7 @@ where
pub fn new(terminal: &'a Mutex<Terminal>) -> Self {
Self {
terminal,
id: None,
padding: Padding::new(0.0),
click_timing: Duration::from_millis(500),
context_menu: None,
@ -57,6 +63,11 @@ where
}
}
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
@ -149,6 +160,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,
@ -427,7 +450,7 @@ where
Event::Keyboard(KeyEvent::KeyPressed {
key_code,
modifiers,
}) => match (
}) if state.is_focused => match (
modifiers.logo(),
modifiers.control(),
modifiers.alt(),
@ -670,7 +693,7 @@ where
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
state.modifiers = modifiers;
}
Event::Keyboard(KeyEvent::CharacterReceived(character)) => {
Event::Keyboard(KeyEvent::CharacterReceived(character)) if state.is_focused => {
match (
state.modifiers.logo(),
state.modifiers.control(),
@ -717,6 +740,8 @@ where
}
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 = p.x - self.padding.left;
@ -915,6 +940,7 @@ pub struct State {
modifiers: Modifiers,
click: Option<(ClickKind, Instant)>,
dragging: Option<Dragging>,
is_focused: bool,
scroll_pixels: f32,
scrollbar_rect: Cell<Rectangle<f32>>,
}
@ -926,8 +952,23 @@ impl State {
modifiers: Modifiers::empty(),
click: None,
dragging: None,
is_focused: false,
scroll_pixels: 0.0,
scrollbar_rect: Cell::new(Rectangle::default()),
}
}
}
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;
}
}