feat: rebase libcosmic onto iced 0.14

This commit is contained in:
Ashley Wulber 2026-03-18 12:23:49 -04:00 committed by GitHub
parent 6c3d0b2770
commit 6326f65d84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 1379 additions and 1344 deletions

2442
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@ version = "1.0.8"
authors = ["Jeremy Soller <jeremy@system76.com>"] authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2024" edition = "2024"
license = "GPL-3.0-only" license = "GPL-3.0-only"
rust-version = "1.85" rust-version = "1.90"
[dependencies] [dependencies]
dirs = "6" dirs = "6"
@ -71,6 +71,6 @@ onig = { git = "https://github.com/rust-onig/rust-onig.git", branch = "main" }
onig_sys = { git = "https://github.com/rust-onig/rust-onig.git", branch = "main" } onig_sys = { git = "https://github.com/rust-onig/rust-onig.git", branch = "main" }
# [patch.'https://github.com/pop-os/libcosmic'] # [patch.'https://github.com/pop-os/libcosmic']
# libcosmic = { path = "../libcosmic" } # libcosmic = { git = "https://github.com/pop-os/libcosmic//" }
# cosmic-config = { path = "../libcosmic/cosmic-config" } # cosmic-config = { git = "https://github.com/pop-os/libcosmic//" }
# cosmic-theme = { path = "../libcosmic/cosmic-theme" } # cosmic-theme = { git = "https://github.com/pop-os/libcosmic//" }

View file

@ -28,6 +28,7 @@ use cosmic_files::{
use cosmic_text::{Cursor, Edit, Family, Selection, SwashCache, SyntaxSystem, ViMode}; use cosmic_text::{Cursor, Edit, Family, Selection, SwashCache, SyntaxSystem, ViMode};
use notify::{RecursiveMode, Watcher}; use notify::{RecursiveMode, Watcher};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::hash::Hash;
use std::{ use std::{
any::TypeId, any::TypeId,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
@ -466,7 +467,7 @@ pub struct App {
theme_names: Vec<String>, theme_names: Vec<String>,
context_page: ContextPage, context_page: ContextPage,
text_box_id: widget::Id, text_box_id: widget::Id,
auto_scroll: Option<f32>, auto_scroll: Option<(f32, u32)>,
dialog_opt: Option<Dialog<Message>>, dialog_opt: Option<Dialog<Message>>,
dialog_page_opt: Option<DialogPage>, dialog_page_opt: Option<DialogPage>,
find_opt: Option<FindField>, find_opt: Option<FindField>,
@ -1092,7 +1093,7 @@ impl App {
widget::row::with_children(vec![ widget::row::with_children(vec![
icon.into(), icon.into(),
widget::text(text.clone()).into(), widget::text(text.clone()).into(),
widget::horizontal_space().into(), widget::space::horizontal().into(),
widget::button::standard(fl!("stage")) widget::button::standard(fl!("stage"))
.on_press(Message::GitStage( .on_press(Message::GitStage(
project_path.clone(), project_path.clone(),
@ -1133,7 +1134,7 @@ impl App {
widget::row::with_children(vec![ widget::row::with_children(vec![
icon.into(), icon.into(),
widget::text(text.clone()).into(), widget::text(text.clone()).into(),
widget::horizontal_space().into(), widget::space::horizontal().into(),
widget::button::standard(fl!("unstage")) widget::button::standard(fl!("unstage"))
.on_press(Message::GitUnstage( .on_press(Message::GitUnstage(
project_path.clone(), project_path.clone(),
@ -1672,7 +1673,7 @@ impl Application for App {
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(*entity) { if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(*entity) {
let mut row = widget::row::with_capacity(3).align_y(Alignment::Center); let mut row = widget::row::with_capacity(3).align_y(Alignment::Center);
row = row.push(widget::text(tab.title())); row = row.push(widget::text(tab.title()));
row = row.push(widget::horizontal_space()); row = row.push(widget::space::horizontal());
if let Some(_path) = &tab.path_opt { if let Some(_path) = &tab.path_opt {
row = row.push( row = row.push(
widget::button::standard(fl!("save")) widget::button::standard(fl!("save"))
@ -1741,7 +1742,14 @@ impl Application for App {
return self.update_config(); return self.update_config();
} }
Message::AutoScroll(auto_scroll) => { Message::AutoScroll(auto_scroll) => {
self.auto_scroll = auto_scroll; self.auto_scroll = auto_scroll.map(|new| {
(
new,
self.auto_scroll
.map(|old| old.1.wrapping_add(1))
.unwrap_or_default(),
)
});
} }
Message::Config(config) => { Message::Config(config) => {
if config != self.config { if config != self.config {
@ -3186,7 +3194,7 @@ impl Application for App {
widget::tooltip::Position::Top, widget::tooltip::Position::Top,
) )
.into(), .into(),
widget::horizontal_space().into(), widget::space::horizontal().into(),
button::custom(icon_cache_get("window-close-symbolic", 16)) button::custom(icon_cache_get("window-close-symbolic", 16))
.on_press(Message::Find(None)) .on_press(Message::Find(None))
.padding(space_xxs) .padding(space_xxs)
@ -3243,13 +3251,16 @@ impl Application for App {
column = column.push( column = column.push(
widget::row::with_children(vec![ widget::row::with_children(vec![
widget::checkbox(fl!("case-sensitive"), self.config.find_case_sensitive) widget::checkbox(self.config.find_case_sensitive)
.label(fl!("case-sensitive"))
.on_toggle(Message::FindCaseSensitive) .on_toggle(Message::FindCaseSensitive)
.into(), .into(),
widget::checkbox(fl!("use-regex"), self.config.find_use_regex) widget::checkbox(self.config.find_use_regex)
.label(fl!("use-regex"))
.on_toggle(Message::FindUseRegex) .on_toggle(Message::FindUseRegex)
.into(), .into(),
widget::checkbox(fl!("wrap-around"), self.config.find_wrap_around) widget::checkbox(self.config.find_wrap_around)
.label(fl!("wrap-around"))
.on_toggle(Message::FindWrapAround) .on_toggle(Message::FindWrapAround)
.into(), .into(),
]) ])
@ -3299,67 +3310,74 @@ impl Application for App {
} }
_ => None, _ => None,
}), }),
Subscription::run_with_id( Subscription::run_with(TypeId::of::<WatcherSubscription>(), |_| {
TypeId::of::<WatcherSubscription>(), stream::channel(
stream::channel(100, |mut output| async move { 100,
let watcher_res = { |mut output: futures::channel::mpsc::Sender<Message>| async move {
let mut output = output.clone(); let watcher_res = {
//TODO: debounce let mut output = output.clone();
notify::recommended_watcher( //TODO: debounce
move |event_res: Result<notify::Event, notify::Error>| match event_res { notify::recommended_watcher(
Ok(event) => { move |event_res: Result<notify::Event, notify::Error>| {
match &event.kind { match event_res {
notify::EventKind::Access(_) Ok(event) => {
| notify::EventKind::Modify( match &event.kind {
notify::event::ModifyKind::Metadata(_), notify::EventKind::Access(_)
) => { | notify::EventKind::Modify(
// Data not mutated notify::event::ModifyKind::Metadata(_),
return; ) => {
} // Data not mutated
_ => {} return;
} }
_ => {}
}
match futures::executor::block_on(async { match futures::executor::block_on(async {
output.send(Message::NotifyEvent(event)).await output.send(Message::NotifyEvent(event)).await
}) { }) {
Ok(()) => {} Ok(()) => {}
Err(err) => {
log::warn!(
"failed to send notify event: {:?}",
err
);
}
}
}
Err(err) => { Err(err) => {
log::warn!("failed to send notify event: {:?}", err); log::warn!("failed to watch files: {:?}", err);
} }
} }
} },
Err(err) => { )
log::warn!("failed to watch files: {:?}", err); };
}
},
)
};
match watcher_res { match watcher_res {
Ok(watcher) => { Ok(watcher) => {
match output match output
.send(Message::NotifyWatcher(WatcherWrapper { .send(Message::NotifyWatcher(WatcherWrapper {
watcher_opt: Some(watcher), watcher_opt: Some(watcher),
})) }))
.await .await
{ {
Ok(()) => {} Ok(()) => {}
Err(err) => { Err(err) => {
log::warn!("failed to send notify watcher: {:?}", err); log::warn!("failed to send notify watcher: {:?}", err);
}
} }
} }
Err(err) => {
log::warn!("failed to create file watcher: {:?}", err);
}
} }
Err(err) => {
log::warn!("failed to create file watcher: {:?}", err);
}
}
//TODO: how to properly kill this task? //TODO: how to properly kill this task?
loop { loop {
time::sleep(time::Duration::new(1, 0)).await; time::sleep(time::Duration::new(1, 0)).await;
} }
}), },
), )
}),
cosmic_config::config_subscription( cosmic_config::config_subscription(
TypeId::of::<ConfigSubscription>(), TypeId::of::<ConfigSubscription>(),
Self::APP_ID.into(), Self::APP_ID.into(),
@ -3403,9 +3421,24 @@ impl Application for App {
]; ];
if let Some(auto_scroll) = self.auto_scroll { if let Some(auto_scroll) = self.auto_scroll {
#[derive(Clone, Copy)]
struct AutoScroll {
counter: u32,
auto_scroll: f32,
}
impl Hash for AutoScroll {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.counter.hash(state);
}
}
subscriptions.push( subscriptions.push(
iced::time::every(time::Duration::from_millis(10)) iced::time::every(time::Duration::from_millis(10))
.map(move |_| Message::Scroll(auto_scroll)), .with(AutoScroll {
auto_scroll: auto_scroll.0,
counter: auto_scroll.1,
})
.map(move |(auto_scroll, _)| Message::Scroll(auto_scroll.auto_scroll)),
); );
} }

View file

@ -9,9 +9,9 @@ use cosmic::{
iced_core::Border, iced_core::Border,
theme, theme,
widget::{ widget::{
self, divider, horizontal_space, self, divider,
menu::{ItemHeight, ItemWidth, menu_button}, menu::{ItemHeight, ItemWidth, menu_button},
responsive_menu_bar, segmented_button, responsive_menu_bar, segmented_button, space,
}, },
}; };
use std::{collections::HashMap, path::PathBuf, sync::LazyLock}; use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
@ -132,7 +132,7 @@ pub fn context_menu<'a>(
} }
menu_button(vec![ menu_button(vec![
widget::text(menu_label).into(), widget::text(menu_label).into(),
horizontal_space().into(), space::horizontal().into(),
widget::text(key) widget::text(key)
.class(theme::Text::Custom(key_style)) .class(theme::Text::Custom(key_style))
.into(), .into(),

View file

@ -273,7 +273,7 @@ where
} }
fn layout( fn layout(
&self, &mut self,
_tree: &mut widget::Tree, _tree: &mut widget::Tree,
_renderer: &Renderer, _renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
@ -303,15 +303,15 @@ where
} }
fn operate( fn operate(
&self, &mut self,
tree: &mut widget::Tree, tree: &mut widget::Tree,
_layout: Layout<'_>, layout: Layout<'_>,
_renderer: &Renderer, _renderer: &Renderer,
operation: &mut dyn Operation, operation: &mut dyn Operation,
) { ) {
let state = tree.state.downcast_mut::<State>(); let state = tree.state.downcast_mut::<State>();
operation.focusable(state, self.id.as_ref()); operation.focusable(self.id.as_ref(), layout.bounds(), state);
} }
fn mouse_interaction( fn mouse_interaction(
@ -371,10 +371,10 @@ where
let view_position = layout.position() + [self.padding.left, self.padding.top].into(); let view_position = layout.position() + [self.padding.left, self.padding.top].into();
let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32) let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32)
- self.padding.horizontal() as i32 - self.padding.x() as i32
- scrollbar_size; - scrollbar_size;
let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32) let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32)
- self.padding.vertical() as i32; - self.padding.y() as i32;
let scale_factor = style.scale_factor as f32; let scale_factor = style.scale_factor as f32;
let metrics = self.metrics.scale(scale_factor); let metrics = self.metrics.scale(scale_factor);
@ -657,18 +657,26 @@ where
renderer.with_transformation(Transformation::scale(1.0 / scale_factor), |renderer| { renderer.with_transformation(Transformation::scale(1.0 / scale_factor), |renderer| {
// Draw cached image (only has line numbers) // Draw cached image (only has line numbers)
if let Some(ref handle) = *handle_opt { if let Some(ref handle) = *handle_opt {
let image_size = image::Renderer::measure_image(renderer, handle); let image_size = image::Renderer::measure_image(renderer, handle)
.unwrap_or_else(|| Size::new(1, 1));
image::Renderer::draw_image( image::Renderer::draw_image(
renderer, renderer,
handle.clone(), image::Image {
image::FilterMethod::Nearest, handle: handle.clone(),
filter_method: image::FilterMethod::Nearest,
rotation: Radians(0.0),
border_radius: [0.0; 4].into(),
opacity: 1.0,
snap: false,
},
Rectangle::new(
Point::new(0.0, 0.0),
Size::new(image_size.width as f32, image_size.height as f32),
),
Rectangle::new( Rectangle::new(
Point::new(0.0, 0.0), Point::new(0.0, 0.0),
Size::new(image_size.width as f32, image_size.height as f32), Size::new(image_size.width as f32, image_size.height as f32),
), ),
Radians(0.0),
1.0,
[0.0; 4],
); );
} }
@ -722,7 +730,7 @@ where
renderer.fill_raw(Raw { renderer.fill_raw(Raw {
buffer: Arc::downgrade(&buffer), buffer: Arc::downgrade(&buffer),
position: pos, position: pos,
color: Color::new(1.0, 1.0, 1.0, 1.0), color: Color::from_rgba(1.0, 1.0, 1.0, 1.0),
clip_bounds, clip_bounds,
}); });
} }
@ -922,17 +930,17 @@ where
log::trace!("redraw {}, {}: {:?}", view_w, view_h, duration); log::trace!("redraw {}, {}: {:?}", view_w, view_h, duration);
} }
fn on_event( fn update(
&mut self, &mut self,
tree: &mut widget::Tree, tree: &mut widget::Tree,
event: Event, event: &Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: mouse::Cursor, cursor_position: mouse::Cursor,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: &mut dyn Clipboard, _clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
_viewport: &Rectangle<f32>, _viewport: &Rectangle<f32>,
) -> Status { ) {
let state = tree.state.downcast_mut::<State>(); let state = tree.state.downcast_mut::<State>();
let editor_offset_x = state.editor_offset_x.get(); let editor_offset_x = state.editor_offset_x.get();
let scale_factor = state.scale_factor.get(); let scale_factor = state.scale_factor.get();
@ -1003,62 +1011,61 @@ where
shell.publish(on_focus.clone()); shell.publish(on_focus.clone());
} }
let mut status = Status::Ignored;
match event { match event {
Event::Keyboard(KeyEvent::KeyPressed { Event::Keyboard(KeyEvent::KeyPressed {
modified_key: Key::Named(key), modified_key: Key::Named(key),
modifiers, modifiers,
.. ..
}) if state.is_focused && !matches!(key, Named::Space) => match key { }) if state.is_focused => match key {
Named::ArrowLeft => { Named::ArrowLeft => {
motion_modifiers(&mut editor, Motion::Left, modifiers); motion_modifiers(&mut editor, Motion::Left, *modifiers);
status = Status::Captured; shell.capture_event();
} }
Named::ArrowRight => { Named::ArrowRight => {
motion_modifiers(&mut editor, Motion::Right, modifiers); motion_modifiers(&mut editor, Motion::Right, *modifiers);
status = Status::Captured; shell.capture_event();
} }
Named::ArrowUp => { Named::ArrowUp => {
motion_modifiers(&mut editor, Motion::Up, modifiers); motion_modifiers(&mut editor, Motion::Up, *modifiers);
status = Status::Captured; shell.capture_event();
} }
Named::ArrowDown => { Named::ArrowDown => {
motion_modifiers(&mut editor, Motion::Down, modifiers); motion_modifiers(&mut editor, Motion::Down, *modifiers);
status = Status::Captured; shell.capture_event();
} }
Named::Home => { Named::Home => {
motion_modifiers(&mut editor, Motion::Home, modifiers); motion_modifiers(&mut editor, Motion::Home, *modifiers);
status = Status::Captured; shell.capture_event();
} }
Named::End => { Named::End => {
motion_modifiers(&mut editor, Motion::End, modifiers); motion_modifiers(&mut editor, Motion::End, *modifiers);
status = Status::Captured; shell.capture_event();
} }
Named::PageUp => { Named::PageUp => {
motion_modifiers(&mut editor, Motion::PageUp, modifiers); motion_modifiers(&mut editor, Motion::PageUp, *modifiers);
status = Status::Captured; shell.capture_event();
} }
Named::PageDown => { Named::PageDown => {
motion_modifiers(&mut editor, Motion::PageDown, modifiers); motion_modifiers(&mut editor, Motion::PageDown, *modifiers);
status = Status::Captured; shell.capture_event();
} }
Named::Escape => { Named::Escape => {
editor.action(Action::Escape); editor.action(Action::Escape);
status = Status::Captured; shell.capture_event();
} }
Named::Enter => { Named::Enter => {
editor.action(Action::Enter); editor.action(Action::Enter);
status = Status::Captured; shell.capture_event();
} }
Named::Backspace => { Named::Backspace => {
delete_modifiers(&mut editor, Motion::LeftWord, modifiers); delete_modifiers(&mut editor, Motion::LeftWord, *modifiers);
editor.action(Action::Backspace); editor.action(Action::Backspace);
status = Status::Captured; shell.capture_event();
} }
Named::Delete => { Named::Delete => {
delete_modifiers(&mut editor, Motion::RightWord, modifiers); delete_modifiers(&mut editor, Motion::RightWord, *modifiers);
editor.action(Action::Delete); editor.action(Action::Delete);
status = Status::Captured; shell.capture_event();
} }
Named::Tab => { Named::Tab => {
if !modifiers.control() && !modifiers.alt() { if !modifiers.control() && !modifiers.alt() {
@ -1067,19 +1074,24 @@ where
} else { } else {
editor.action(Action::Indent); editor.action(Action::Indent);
} }
status = Status::Captured; shell.capture_event();
} }
} }
_ => (), _ => (),
}, },
Event::Keyboard(KeyEvent::KeyPressed { text, .. }) if state.is_focused => { Event::Keyboard(KeyEvent::KeyPressed { text, .. }) if state.is_focused => {
let character = text.unwrap_or_default().chars().next().unwrap_or_default(); let character = text
.clone()
.unwrap_or_default()
.chars()
.next()
.unwrap_or_default();
// Only parse keys when Super, Ctrl, and Alt are not pressed // Only parse keys when Super, Ctrl, and Alt are not pressed
if !state.modifiers.logo() && !state.modifiers.control() && !state.modifiers.alt() { if !state.modifiers.logo() && !state.modifiers.control() && !state.modifiers.alt() {
if !character.is_control() { if !character.is_control() {
editor.action(Action::Insert(character)); editor.action(Action::Insert(character));
} }
status = Status::Captured; shell.capture_event();
} }
} }
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => { Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
@ -1089,7 +1101,7 @@ where
} else if !modifiers.shift() && state.modifiers.shift() { } else if !modifiers.shift() && state.modifiers.shift() {
*state.shift_anchor.lock().unwrap() = None; *state.shift_anchor.lock().unwrap() = None;
} }
state.modifiers = modifiers; state.modifiers = *modifiers;
} }
Event::Mouse(MouseEvent::ButtonPressed(button)) => { Event::Mouse(MouseEvent::ButtonPressed(button)) => {
if let Some(p) = cursor_position.position_in(layout.bounds()) { if let Some(p) = cursor_position.position_in(layout.bounds()) {
@ -1207,14 +1219,14 @@ where
})); }));
} }
status = Status::Captured; shell.capture_event();
} else { } else {
state.is_focused = false; state.is_focused = false;
} }
} }
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => { Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
state.dragging = None; state.dragging = None;
status = Status::Captured; shell.capture_event();
if let Some(on_auto_scroll) = &self.on_auto_scroll { if let Some(on_auto_scroll) = &self.on_auto_scroll {
shell.publish(on_auto_scroll(None)); shell.publish(on_auto_scroll(None));
} }
@ -1289,7 +1301,7 @@ where
} }
} }
} }
status = Status::Captured; shell.capture_event();
} }
} }
Event::Mouse(MouseEvent::WheelScrolled { delta }) => { Event::Mouse(MouseEvent::WheelScrolled { delta }) => {
@ -1321,7 +1333,7 @@ where
.max(0.0); .max(0.0);
buffer.set_scroll(scroll); buffer.set_scroll(scroll);
}); });
status = Status::Captured; shell.capture_event();
} }
} }
_ => (), _ => (),
@ -1336,8 +1348,6 @@ where
shell.publish(on_changed.clone()); shell.publish(on_changed.clone());
} }
} }
status
} }
} }