diff --git a/README.md b/README.md index e84f9ed..e3b791b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ > search bar that people can use to do many things, like opening apps, > calculators, quick-notes, etc. -![RustCast Demo PreRelease V1](./docs/rustcast-v0-3-4.png) +![RustCast Demo PreRelease V1](./docs/rustcast-v0-4-5.png) ## Installation: diff --git a/docs/default.toml b/docs/default.toml index e81e61e..698317d 100644 --- a/docs/default.toml +++ b/docs/default.toml @@ -13,8 +13,11 @@ clear_on_enter = true [theme] text_color = [0.95, 0.95, 0.96] -background_color = [0.0, 0.0, 0.0] -background_opacity = 0.25 +background_color = [0.09, 0.09, 0.09] +# Background opacity should / can be omitted from 0.4.5 onwards, +# as rustcast doesn't support custom opacity's anymore, +# this field will have no effect on rustcast +background_opacity = 0.0 blur = false show_icons = true show_scroll_bar = true diff --git a/docs/index.html b/docs/index.html index b874642..f7828ca 100644 --- a/docs/index.html +++ b/docs/index.html @@ -98,7 +98,7 @@

- RustCast Demo + RustCast Demo
( - &'a self, - theme: &'a crate::config::Theme, + pub fn render( + self, + theme: crate::config::Theme, id_num: u32, focussed_id: u32, - ) -> impl Into> { - let mut tile = Row::new().width(Fill).height(55); + ) -> iced::Element<'static, Message> { + let focused = focussed_id == id_num; + + // Title + subtitle (Raycast style) + let text_block = iced::widget::Column::new() + .spacing(2) + .push( + Text::new(self.name) + .font(theme.font()) + .size(16) + .color(theme.text_color(1.0)), + ) + .push( + Text::new(self.desc) + .font(theme.font()) + .size(13) + .color(theme.text_color(0.55)), + ); + + let mut row = Row::new() + .align_y(Alignment::Center) + .width(Fill) + .spacing(10) + .height(50); if theme.show_icons && let Some(icon) = &self.icons { - tile = tile - .push(container(Viewer::new(icon).height(35).width(35))) - .align_y(Alignment::Center); + row = row.push( + container(Viewer::new(icon).height(40).width(40)) + .width(40) + .height(40), + ); } + row = row.push(container(text_block).width(Fill)); - tile = tile.push( - Button::new( - Text::new(&self.name) - .font(theme.font()) - .height(Fill) - .width(Fill) - .color(theme.text_color(1.)) - .align_y(Vertical::Center), - ) - .on_press_maybe({ - match self.open_command.clone() { - AppCommand::Function(func) => Some(Message::RunFunction(func)), - AppCommand::Message(msg) => Some(msg), - AppCommand::Display => None, - } - }) - .style(|_, _| iced::widget::button::Style { - background: None, - text_color: theme.text_color(1.), - ..Default::default() - }) - .width(Fill) - .height(55), - ); + let msg = match self.open_command.clone() { + AppCommand::Function(func) => Some(Message::RunFunction(func)), + AppCommand::Message(msg) => Some(msg), + AppCommand::Display => None, + }; - tile = tile - .push( - container( - Text::new(&self.desc) - .font(theme.font()) - .color(theme.text_color(0.4)), - ) - .padding(12), - ) - .width(Fill); + let theme_clone = theme.clone(); - let (highlight_opacity, border_width) = if focussed_id == id_num { - (0.7, 0.55) - } else { - (0.5, 0.1) - }; + let content = Button::new(row) + .on_press_maybe(msg) + .style(move |_, _| result_button_style(&theme_clone)) + .width(Fill) + .padding(0) + .height(50); - container(tile) + container(content) .id(format!("result-{}", id_num)) - .style(move |_| iced::widget::container::Style { - text_color: Some(theme.text_color(1.)), - background: Some(Background::Color(theme.bg_color())), - border: iced::Border { - color: theme.text_color(highlight_opacity), - width: border_width, - radius: Radius::new(0), - }, - ..Default::default() - }) - .max_height(55) - .padding(5) + .style(move |_| result_row_container_style(&theme, focused)) + .padding(8) .width(Fill) - .height(Fill) + .into() } } diff --git a/src/app/tile.rs b/src/app/tile.rs index d62045f..bcef005 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -84,16 +84,16 @@ impl AppIndex { /// - Page ([`Page`]) the current page of the window (main or clipboard history) #[derive(Clone)] pub struct Tile { - theme: iced::Theme, - focus_id: u32, - query: String, + pub theme: iced::Theme, + pub focus_id: u32, + pub query: String, query_lc: String, results: Vec, options: AppIndex, visible: bool, focused: bool, frontmost: Option>, - config: Config, + pub config: Config, /// The opening hotkey hotkey: HotKey, clipboard_content: Vec, @@ -137,12 +137,10 @@ impl Tile { /// - Window focus changes pub fn subscription(&self) -> Subscription { let keyboard = event::listen_with(|event, _, id| match event { - event::Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => match key { - keyboard::Key::Named(keyboard::key::Named::Escape) => { - Some(Message::EscKeyPressed(id)) - } - _ => None, - }, + event::Event::Keyboard(keyboard::Event::KeyPressed { + key: keyboard::Key::Named(keyboard::key::Named::Escape), + .. + }) => Some(Message::EscKeyPressed(id)), _ => None, }); Subscription::batch([ diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 0e52a09..60000f1 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -15,9 +15,8 @@ use rayon::{ slice::ParallelSliceMut, }; -use crate::app::apps::AppCommand; use crate::app::tile::AppIndex; -use crate::config::Theme; +use crate::styles::{contents_style, rustcast_text_input_style}; use crate::{ app::{Message, Page, apps::App, default_settings, tile::Tile}, config::Config, @@ -93,8 +92,8 @@ pub fn view(tile: &Tile, wid: window::Id) -> Element<'_, Message> { .on_submit(Message::OpenFocused) .id("query") .width(Fill) - .line_height(LineHeight::Relative(1.5)) - .style(|_, _| text_input_style(&tile.config.theme)) + .line_height(LineHeight::Relative(1.75)) + .style(|_, status| rustcast_text_input_style(&tile.config.theme, status)) .padding(20); let scrollbar_direction = if tile.config.theme.show_scroll_bar { @@ -107,70 +106,42 @@ pub fn view(tile: &Tile, wid: window::Id) -> Element<'_, Message> { } else { Direction::Vertical(Scrollbar::hidden()) }; - let results = match tile.page { - Page::Main => { - let mut search_results = Column::new(); - for (i, result) in tile.results.iter().enumerate() { - search_results = search_results.push(result.render( - &tile.config.theme, - i as u32, - tile.focus_id, - )); - } - search_results - } - Page::ClipboardHistory => { - let mut clipboard_history = Column::new(); - for result in &tile.clipboard_content { - clipboard_history = clipboard_history - .push(result.render_clipboard_item(tile.config.theme.clone())); - } - clipboard_history - } + + let results = if tile.page == Page::ClipboardHistory { + Column::from_iter( + tile.clipboard_content + .iter() + .enumerate() + .map(|(i, content)| { + content + .to_app() + .render(tile.config.theme.clone(), i as u32, tile.focus_id) + }), + ) + } else { + Column::from_iter(tile.results.iter().enumerate().map(|(i, app)| { + app.clone() + .render(tile.config.theme.clone(), i as u32, tile.focus_id) + })) }; - let scrollable = Scrollable::with_direction(results, scrollbar_direction).id("results"); - let contents = Column::new().push(title_input).push(scrollable); - container(contents) - .style(|_| iced::widget::container::Style { - background: None, + let scrollable = Scrollable::with_direction(results, scrollbar_direction).id("results"); + let contents = container(Column::new().push(title_input).push(scrollable).spacing(0)) + .style(|_| container::Style { text_color: None, + background: None, border: iced::Border { - color: tile.config.theme.text_color(1.), + color: Color::WHITE, width: 1., - radius: Radius::new(0), + radius: Radius::new(5), }, ..Default::default() - }) - .padding(0) - .clip(true) + }); + + container(contents.clip(true)) + .style(|_| contents_style(&tile.config.theme)) .into() } else { space().into() } } - -fn text_input_style(theme: &Theme) -> iced::widget::text_input::Style { - text_input::Style { - background: iced::Background::Color(Color::TRANSPARENT), - border: iced::Border { - color: iced::Color { - r: 0.95, - g: 0.95, - b: 0.95, - a: 0.7, - }, - width: 0.5, - radius: iced::border::Radius { - top_left: 0., - top_right: 0., - bottom_right: 0., - bottom_left: 0., - }, - }, - icon: theme.text_color(0.), - placeholder: theme.text_color(0.7), - value: theme.text_color(1.), - selection: theme.text_color(0.2), - } -} diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 46cd75b..6a83322 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -219,7 +219,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { id, iced::Size { width: WINDOW_WIDTH, - height: ((max_elem * 55) + DEFAULT_WINDOW_HEIGHT as usize) as f32, + height: ((max_elem * 70) + DEFAULT_WINDOW_HEIGHT as usize) as f32, }, ), Task::done(Message::ChangeFocus(ArrowKey::Left)), @@ -230,7 +230,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { id, iced::Size { width: WINDOW_WIDTH, - height: ((element_count * 55) + DEFAULT_WINDOW_HEIGHT as usize) as f32, + height: ((element_count * 70) + DEFAULT_WINDOW_HEIGHT as usize) as f32, }, ) } else { diff --git a/src/clipboard.rs b/src/clipboard.rs index ec671f6..07d6d87 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -1,12 +1,7 @@ //! This has all the logic regarding the cliboard history use arboard::ImageData; -use iced::{ - Length::Fill, - alignment::Vertical, - widget::{Button, Row, Text, container}, -}; -use crate::{app::Message, commands::Function, config::Theme as ConfigTheme}; +use crate::{app::apps::App, commands::Function}; /// The kinds of clipboard content that rustcast can handle and their contents #[derive(Debug, Clone)] @@ -17,60 +12,23 @@ pub enum ClipBoardContentType { impl ClipBoardContentType { /// Returns the iced element for rendering the clipboard item - pub fn render_clipboard_item( - &self, - theme: ConfigTheme, - ) -> impl Into> { - let mut tile = Row::new().width(Fill).height(55); - - let text = match self { - ClipBoardContentType::Text(text) => text, - ClipBoardContentType::Image(_) => "", + pub fn to_app(&self) -> App { + let name = match self { + ClipBoardContentType::Image(_) => "".to_string(), + ClipBoardContentType::Text(a) => a.to_owned(), }; - let text_color = theme.text_color(1.); - let text_color_clone = text_color; - - tile = tile.push( - container( - Button::new( - Text::new(text.to_owned()) - .font(theme.font()) - .height(Fill) - .width(Fill) - .align_y(Vertical::Center), - ) - .on_press(Message::RunFunction(Function::CopyToClipboard( - self.to_owned(), - ))) - .style(move |_, _| iced::widget::button::Style { - background: None, - text_color: text_color_clone, - ..Default::default() - }) - .width(Fill) - .height(55), - ) - .style(move |_| iced::widget::container::Style { - text_color: None, - background: None, - border: iced::Border { - color: theme.text_color(0.5), - width: 0.1, - radius: iced::border::Radius::new(0), - }, - ..Default::default() - }), - ); + let self_clone = self.clone(); - container(tile) - .style(move |_| iced::widget::container::Style { - text_color: Some(text_color), - background: None, - ..Default::default() - }) - .width(Fill) - .height(Fill) + App { + open_command: crate::app::apps::AppCommand::Function(Function::CopyToClipboard( + self_clone.to_owned(), + )), + desc: "Clipboard Item".to_string(), + icons: None, + name_lc: name.to_lowercase(), + name, + } } } diff --git a/src/config.rs b/src/config.rs index 7fa8dc7..5fa79e5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -46,7 +46,6 @@ impl Default for Config { pub struct Theme { pub text_color: (f32, f32, f32), pub background_color: (f32, f32, f32), - pub background_opacity: f32, pub blur: bool, pub show_icons: bool, pub show_scroll_bar: bool, @@ -57,8 +56,7 @@ impl Default for Theme { fn default() -> Self { Self { text_color: (0.95, 0.95, 0.96), - background_color: (0., 0., 0.), - background_opacity: 0.25, + background_color: (0.09, 0.09, 0.09), blur: false, show_icons: true, show_scroll_bar: true, @@ -119,7 +117,7 @@ impl Theme { r: self.background_color.0, g: self.background_color.1, b: self.background_color.2, - a: self.background_opacity, + a: 0., } } diff --git a/src/main.rs b/src/main.rs index 633dd5f..cb1ef46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod commands; mod config; mod haptics; mod macos; +mod styles; mod utils; use std::path::Path; diff --git a/src/styles.rs b/src/styles.rs new file mode 100644 index 0000000..8154ff4 --- /dev/null +++ b/src/styles.rs @@ -0,0 +1,89 @@ +use iced::border::Radius; +use iced::widget::text_input::Status; +use iced::widget::{button, container}; +use iced::{Background, Border, Color, widget::text_input}; + +use crate::config::Theme as ConfigTheme; + +/// Helper: mix base color with white (simple “tint”) +pub fn tint(mut c: Color, amount: f32) -> Color { + c.r = c.r + (1.0 - c.r) * amount; + c.g = c.g + (1.0 - c.g) * amount; + c.b = c.b + (1.0 - c.b) * amount; + c +} + +/// Helper: apply alpha +pub fn with_alpha(mut c: Color, a: f32) -> Color { + c.a = a; + c +} + +pub fn rustcast_text_input_style(theme: &ConfigTheme, status: Status) -> text_input::Style { + let base_bg = theme.bg_color(); + let surface = with_alpha(tint(base_bg, 0.06), 1.0); + + let (border_color, border_width) = match status { + text_input::Status::Focused { .. } => (theme.text_color(0.20), 1.), + text_input::Status::Hovered => (theme.text_color(0.20), 1.), + text_input::Status::Active => (theme.text_color(0.20), 1.), + text_input::Status::Disabled => (theme.text_color(0.20), 1.), + }; + + text_input::Style { + background: Background::Color(surface), + border: Border { + color: border_color, + width: border_width, + radius: Radius::new(5.0), + }, + icon: theme.text_color(0.7), + placeholder: theme.text_color(0.45), + value: theme.text_color(1.0), + selection: theme.text_color(0.2), + } +} + +pub fn contents_style(theme: &ConfigTheme) -> container::Style { + container::Style { + background: None, + text_color: None, + border: iced::Border { + color: theme.text_color(0.7), + width: 1.0, + radius: Radius::new(14.0), + }, + ..Default::default() + } +} + +pub fn result_button_style(theme: &ConfigTheme) -> button::Style { + button::Style { + text_color: theme.text_color(1.), + background: Some(Background::Color(theme.bg_color())), + ..Default::default() + } +} + +pub fn result_row_container_style(tile: &ConfigTheme, focused: bool) -> container::Style { + let base = tile.bg_color(); + let row_bg = if focused { + with_alpha(tint(base, 0.10), 1.0) + } else { + with_alpha(tint(base, 0.04), 1.0) + }; + + container::Style { + background: Some(Background::Color(row_bg)), + border: Border { + color: if focused { + tile.text_color(0.35) + } else { + tile.text_color(0.10) + }, + width: 0.2, + radius: Radius::new(0.), + }, + ..Default::default() + } +}