Skip to content

Commit 381e63e

Browse files
committed
Add support for clipboard history
1 parent 3353a72 commit 381e63e

5 files changed

Lines changed: 139 additions & 67 deletions

File tree

src/app.rs

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
use crate::commands::{ClipBoardContentType, ClipboardContent, Function};
1+
use crate::clipboard::ClipBoardContentType;
2+
use crate::commands::Function;
23
use crate::config::Config;
34
use crate::macos::{focus_this_app, transform_process_to_ui_element};
45
use crate::{macos, utils::get_installed_apps};
56

6-
use arboard::{Clipboard, ImageData};
7+
use arboard::Clipboard;
78
use global_hotkey::{GlobalHotKeyEvent, HotKeyState};
89
use iced::futures::SinkExt;
910
use iced::{
@@ -14,7 +15,7 @@ use iced::{
1415
stream,
1516
widget::{
1617
Button, Column, Row, Text, container, image::Viewer, operation, scrollable, space,
17-
text::LineHeight, text_input,
18+
text_input,
1819
},
1920
window::{self, Id, Settings},
2021
};
@@ -56,6 +57,7 @@ impl App {
5657
},
5758
]
5859
}
60+
5961
pub fn render(&self, show_icons: bool) -> impl Into<iced::Element<'_, Message>> {
6062
let mut tile = Row::new().width(Fill).height(55);
6163

@@ -106,6 +108,12 @@ impl App {
106108
}
107109
}
108110

111+
#[derive(Debug, Clone, PartialEq)]
112+
pub enum Page {
113+
Main,
114+
ClipboardHistory,
115+
}
116+
109117
#[derive(Debug, Clone)]
110118
pub enum Message {
111119
OpenWindow,
@@ -118,7 +126,6 @@ pub enum Message {
118126
ClearSearchQuery,
119127
ReloadConfig,
120128
ClipboardHistory(ClipBoardContentType),
121-
ShowClipboardHistory,
122129
_Nothing,
123130
}
124131

@@ -151,7 +158,8 @@ pub struct Tile {
151158
frontmost: Option<Retained<NSRunningApplication>>,
152159
config: Config,
153160
open_hotkey_id: u32,
154-
clipboard_content: Vec<ClipboardContent>,
161+
clipboard_content: Vec<ClipBoardContentType>,
162+
page: Page,
155163
}
156164

157165
impl Tile {
@@ -202,6 +210,7 @@ impl Tile {
202210
theme: config.theme.to_owned().to_iced_theme(),
203211
open_hotkey_id: keybind_id,
204212
clipboard_content: vec![],
213+
page: Page::Main,
205214
},
206215
Task::batch([open.map(|_| Message::OpenWindow)]),
207216
)
@@ -255,20 +264,33 @@ impl Tile {
255264
id,
256265
iced::Size::new(WINDOW_WIDTH, 55. + DEFAULT_WINDOW_HEIGHT),
257266
);
267+
} else if self.query_lc == "cbhist" {
268+
self.page = Page::ClipboardHistory
269+
} else if self.query_lc == "main" {
270+
self.page = Page::Main
258271
}
259272

260273
self.handle_search_query_changed();
261274
let new_length = self.results.len();
262275

263276
let max_elem = min(5, new_length);
264-
if prev_size != new_length {
277+
if prev_size != new_length && self.page == Page::Main {
265278
window::resize(
266279
id,
267280
iced::Size {
268281
width: WINDOW_WIDTH,
269282
height: ((max_elem * 55) + DEFAULT_WINDOW_HEIGHT as usize) as f32,
270283
},
271284
)
285+
} else if self.page == Page::ClipboardHistory {
286+
let element_count = min(self.clipboard_content.len(), 5);
287+
window::resize(
288+
id,
289+
iced::Size {
290+
width: WINDOW_WIDTH,
291+
height: ((element_count * 55) + DEFAULT_WINDOW_HEIGHT as usize) as f32,
292+
},
293+
)
272294
} else {
273295
Task::none()
274296
}
@@ -353,13 +375,10 @@ impl Tile {
353375
}
354376

355377
Message::ClipboardHistory(clip_content) => {
356-
self.clipboard_content
357-
.push(ClipboardContent::from_content_type(clip_content));
378+
self.clipboard_content.push(clip_content);
358379
Task::none()
359380
}
360381

361-
Message::ShowClipboardHistory => Task::none(),
362-
363382
Message::_Nothing => Task::none(),
364383
}
365384
}
@@ -378,19 +397,32 @@ impl Tile {
378397
})
379398
.id("query")
380399
.width(Fill)
381-
.padding(20)
382-
.line_height(LineHeight::Relative(1.5));
400+
.padding(20);
401+
402+
match self.page {
403+
Page::Main => {
404+
let mut search_results = Column::new();
405+
for result in &self.results {
406+
search_results =
407+
search_results.push(result.render(self.config.theme.show_icons));
408+
}
383409

384-
let mut search_results = Column::new();
385-
for result in &self.results {
386-
search_results =
387-
search_results.push(result.render(self.config.theme.clone().show_icons));
410+
Column::new()
411+
.push(title_input)
412+
.push(scrollable(search_results))
413+
.into()
414+
}
415+
Page::ClipboardHistory => {
416+
let mut clipboard_history = Column::new();
417+
for result in &self.clipboard_content {
418+
clipboard_history = clipboard_history.push(result.render_clipboard_item());
419+
}
420+
Column::new()
421+
.push(title_input)
422+
.push(scrollable(clipboard_history))
423+
.into()
424+
}
388425
}
389-
390-
Column::new()
391-
.push(title_input)
392-
.push(scrollable(search_results))
393-
.into()
394426
} else {
395427
space().into()
396428
}

src/clipboard.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use arboard::ImageData;
2+
use iced::{
3+
Length::Fill,
4+
Theme,
5+
alignment::Vertical,
6+
widget::{Button, Row, Text, container},
7+
};
8+
9+
use crate::{app::Message, commands::Function};
10+
11+
#[derive(Debug, Clone)]
12+
pub enum ClipBoardContentType {
13+
Text(String),
14+
Image(ImageData<'static>),
15+
}
16+
17+
impl ClipBoardContentType {
18+
pub fn render_clipboard_item(&self) -> impl Into<iced::Element<'_, Message>> {
19+
let mut tile = Row::new().width(Fill).height(55);
20+
21+
let text = match self {
22+
ClipBoardContentType::Text(text) => text,
23+
ClipBoardContentType::Image(_) => "<img>",
24+
};
25+
26+
tile = tile.push(
27+
Button::new(
28+
Text::new(text.to_owned())
29+
.height(Fill)
30+
.width(Fill)
31+
.align_y(Vertical::Center),
32+
)
33+
.on_press(Message::RunFunction(Function::CopyToClipboard(
34+
self.to_owned(),
35+
)))
36+
.style(|_, _| iced::widget::button::Style {
37+
background: Some(iced::Background::Color(
38+
Theme::KanagawaDragon.palette().background,
39+
)),
40+
text_color: Theme::KanagawaDragon.palette().text,
41+
..Default::default()
42+
})
43+
.width(Fill)
44+
.height(55),
45+
);
46+
47+
container(tile)
48+
.style(|_| iced::widget::container::Style {
49+
text_color: Some(Theme::KanagawaDragon.palette().text),
50+
background: Some(iced::Background::Color(
51+
Theme::KanagawaDragon.palette().background,
52+
)),
53+
..Default::default()
54+
})
55+
.width(Fill)
56+
.height(Fill)
57+
}
58+
}
59+
60+
impl PartialEq for ClipBoardContentType {
61+
fn eq(&self, other: &Self) -> bool {
62+
if let Self::Text(a) = self
63+
&& let Self::Text(b) = other
64+
{
65+
return a == b;
66+
} else if let Self::Image(image_data) = self
67+
&& let Self::Image(other_image_data) = other
68+
{
69+
return image_data.bytes == other_image_data.bytes;
70+
}
71+
false
72+
}
73+
}

src/commands.rs

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
use std::process::Command;
22

3-
use arboard::{Clipboard, ImageData};
3+
use arboard::Clipboard;
44
use objc2_app_kit::NSWorkspace;
55
use objc2_foundation::NSURL;
66

7-
use crate::{config::Config, utils::get_time_since_epoch};
7+
use crate::{clipboard::ClipBoardContentType, config::Config};
88

99
#[derive(Debug, Clone)]
1010
pub enum Function {
1111
OpenApp(String),
1212
RunShellCommand(Vec<String>),
1313
RandomVar(i32),
14+
CopyToClipboard(ClipBoardContentType),
1415
GoogleSearch(String),
1516
OpenPrefPane,
1617
Quit,
@@ -50,6 +51,15 @@ impl Function {
5051
);
5152
}
5253

54+
Function::CopyToClipboard(clipboard_content) => match clipboard_content {
55+
ClipBoardContentType::Text(text) => {
56+
Clipboard::new().unwrap().set_text(text).ok();
57+
}
58+
ClipBoardContentType::Image(img) => {
59+
Clipboard::new().unwrap().set_image(img.to_owned_img()).ok();
60+
}
61+
},
62+
5363
Function::OpenPrefPane => {
5464
Command::new("open")
5565
.arg(
@@ -63,39 +73,3 @@ impl Function {
6373
}
6474
}
6575
}
66-
67-
#[derive(Debug, Clone)]
68-
pub struct ClipboardContent {
69-
pub content_type: ClipBoardContentType,
70-
pub copied_since_epoch: i64,
71-
}
72-
73-
#[derive(Debug, Clone)]
74-
pub enum ClipBoardContentType {
75-
Text(String),
76-
Image(ImageData<'static>),
77-
}
78-
79-
impl PartialEq for ClipBoardContentType {
80-
fn eq(&self, other: &Self) -> bool {
81-
if let Self::Text(a) = self
82-
&& let Self::Text(b) = other
83-
{
84-
return a == b;
85-
} else if let Self::Image(image_data) = self
86-
&& let Self::Image(other_image_data) = other
87-
{
88-
return image_data.bytes == other_image_data.bytes;
89-
}
90-
false
91-
}
92-
}
93-
94-
impl ClipboardContent {
95-
pub fn from_content_type(content_type: ClipBoardContentType) -> ClipboardContent {
96-
Self {
97-
content_type,
98-
copied_since_epoch: get_time_since_epoch(),
99-
}
100-
}
101-
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod app;
2+
mod clipboard;
23
mod commands;
34
mod config;
45
mod macos;

src/utils.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::{
33
io::Write,
44
path::{Path, PathBuf},
55
process::exit,
6-
time::SystemTime,
76
};
87

98
use global_hotkey::hotkey::Code;
@@ -288,10 +287,3 @@ pub fn to_key_code(key_str: &str) -> Option<Code> {
288287
_ => None,
289288
}
290289
}
291-
292-
pub fn get_time_since_epoch() -> i64 {
293-
SystemTime::now()
294-
.duration_since(SystemTime::UNIX_EPOCH)
295-
.unwrap()
296-
.as_millis() as i64
297-
}

0 commit comments

Comments
 (0)