Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add chat filter window (Ctrl + F) #100

Merged
merged 2 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/gui/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,13 @@ impl ChatWindow {
body.heterogeneous_rows(heights, |mut row| {
let row_index = row.index();
last_visible_row = row_index;
row.col(|ui| {
self.show_regular_chat_single_message(ui, state, ch, row_index);
});
if state.filter.matches(&ch.messages[row_index]) {
row.col(|ui| {
self.show_regular_chat_single_message(
ui, state, ch, row_index,
);
});
}
});
} else {
match state.active_chat_tab_name.as_str() {
Expand Down
144 changes: 144 additions & 0 deletions src/gui/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use eframe::egui;

use steel_core::chat::Message;

use super::state::UIState;

// FIXME: This works on a premise that `input` of all filters is always lowercase, which isn't necessarily true
// (since a user can modify it directly).

// FIXME: A proper solution is keep a copy of all messages in lowercase somewhere to avoid doing that on every iteration.

pub trait FilterCondition: Sized {
fn matches(&self, message: &Message) -> bool;
fn reset(&mut self);
}

#[derive(Debug, Default)]
pub struct UsernameFilter {
pub input: String,
}

impl From<&str> for UsernameFilter {
fn from(value: &str) -> Self {
Self {
input: value.to_lowercase().to_owned(),
}
}
}

impl FilterCondition for UsernameFilter {
fn matches(&self, message: &Message) -> bool {
if self.input.is_empty() {
return true;
}
message.username.to_lowercase().contains(&self.input)
}

fn reset(&mut self) {
self.input.clear();
}
}

#[derive(Debug, Default)]
pub struct TextFilter {
pub input: String,
}

impl From<&str> for TextFilter {
fn from(value: &str) -> Self {
Self {
input: value.to_lowercase().to_owned(),
}
}
}

impl FilterCondition for TextFilter {
fn matches(&self, message: &Message) -> bool {
if self.input.is_empty() {
return true;
}
message.text.to_lowercase().contains(&self.input)
}

fn reset(&mut self) {
self.input.clear();
}
}

#[derive(Debug, Default)]
pub struct FilterCollection {
pub username: UsernameFilter,
pub text: TextFilter,
pub active: bool,
}

impl FilterCollection {
pub fn matches(&self, message: &Message) -> bool {
if !self.active {
return true;
}
self.username.matches(message) && self.text.matches(message)
}

pub fn reset(&mut self) {
self.username.reset();
self.text.reset();
}
}

#[derive(Default)]
pub struct FilterWindow {
show_ui: bool,
}

impl FilterWindow {
pub fn show(&mut self, ctx: &egui::Context, state: &mut UIState) {
self.show_ui = state.filter.active; // Handle menu clicks (chat > find...).
let mut activated_now = false; // Handle Ctrl-F presses that happened during the current frame.

if !self.show_ui && ctx.input(|i| i.modifiers.command && i.key_pressed(egui::Key::F)) {
self.show_ui = true;
state.filter.active = true;
activated_now = true;
}

if self.show_ui && ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
self.show_ui = false;
activated_now = false;
}

egui::Window::new("chat filter")
.auto_sized()
.open(&mut self.show_ui)
.show(ctx, |ui| {
ui.vertical(|ui| {
ui.horizontal(|ui| {
ui.label("message");
let resp = ui.add(
egui::TextEdit::singleline(&mut state.filter.text.input)
.desired_width(150.),
);
if activated_now {
resp.request_focus();
}
});
ui.horizontal(|ui| {
ui.label("username");
ui.add(
egui::TextEdit::singleline(&mut state.filter.username.input)
.desired_width(150.),
);
});
if ui.button("reset").clicked() {
state.filter.reset();
}
});
});

// Deactivate the filter if the window has been closed during the current frame.
if !self.show_ui {
state.filter.active = false;
}
}
}
7 changes: 7 additions & 0 deletions src/gui/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ impl Menu {
response_widget_id: &mut Option<egui::Id>,
) {
ui.menu_button("chat", |ui| {
if ui.button("find...").clicked() {
state.filter.active = true;
ui.close_menu();
}

ui.separator();

let (action, enabled) = match state.connection {
ConnectionStatus::Disconnected { .. } => ("connect".to_owned(), true),
ConnectionStatus::InProgress => ("connecting...".to_owned(), false),
Expand Down
1 change: 1 addition & 0 deletions src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod about;
pub mod chat;
pub mod chat_tabs;
pub mod command;
pub mod filter;
pub mod highlights;
pub mod menu;
pub mod settings;
Expand Down
5 changes: 5 additions & 0 deletions src/gui/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::core::settings::Settings;

use crate::gui::highlights;

use super::filter::FilterCollection;
use super::{HIGHLIGHTS_SEPARATOR, HIGHLIGHTS_TAB_NAME, SERVER_TAB_NAME};

#[derive(Debug)]
Expand Down Expand Up @@ -42,6 +43,8 @@ pub struct UIState {

#[cfg(feature = "glass")]
pub glass: glass::Glass,

pub filter: FilterCollection,
}

impl UIState {
Expand All @@ -60,6 +63,8 @@ impl UIState {

#[cfg(feature = "glass")]
glass: glass::Glass::default(),

filter: FilterCollection::default(),
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/gui/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ pub struct ApplicationWindow {
ui_queue: Receiver<UIMessageIn>,
s: UIState,
date_announcer: DateAnnouncer,
filter_ui: gui::filter::FilterWindow,
}

impl ApplicationWindow {
Expand All @@ -157,6 +158,7 @@ impl ApplicationWindow {
ui_queue,
s: UIState::new(app_queue_handle),
date_announcer: DateAnnouncer::default(),
filter_ui: gui::filter::FilterWindow::default(),
}
}

Expand Down Expand Up @@ -345,6 +347,9 @@ impl eframe::App for ApplicationWindow {
self.menu
.show(ctx, frame, &mut self.s, &mut self.chat.response_widget_id);
self.chat_tabs.show(ctx, &mut self.s);

self.filter_ui.show(ctx, &mut self.s);

self.chat.show(ctx, &self.s);

if !self.menu.dialogs_visible() {
Expand Down
Loading