Skip to content
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
15 changes: 6 additions & 9 deletions src/home/rooms_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1267,15 +1267,12 @@ impl RoomsList {
/// If `false`, the scroll position is preserved, unless it exceeds the new list length,
/// in which case the logic in `draw_walk()` will limit it to the max valid index.
fn update_displayed_rooms(&mut self, cx: &mut Cx, reset_scroll: bool) {
let (mut invited, mut favorites, mut direct, mut regular, mut low_priority) = self.generate_displayed_rooms();
if self.display_filter.is_some()
&& invited.is_empty() && favorites.is_empty()
&& direct.is_empty() && regular.is_empty() && low_priority.is_empty()
{
self.display_filter = RoomDisplayFilter::default();
self.sort_fn = None;
(invited, favorites, direct, regular, low_priority) = self.generate_displayed_rooms();
}
// A filter that matches nothing must show nothing. Clearing the filter
// here instead brought the whole room list back while the user's text
// was still in the box, which reads as the filter being broken — and it
// also made the "no matching rooms" status below unreachable, since
// `display_filter` was already None by the time it ran.
let (invited, favorites, direct, regular, low_priority) = self.generate_displayed_rooms();
self.displayed_invited_rooms = invited;
self.displayed_favorite_rooms = favorites;
self.displayed_direct_rooms = direct;
Expand Down
20 changes: 19 additions & 1 deletion src/shared/icon_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,14 @@ script_mod! {
border_color_focus: (RBX_FOCUS_RING)
border_color_disabled: #0000

color_focus: (RBX_FOCUS_TINT)
// A soft wash, not the saturated accent. The shader replaces the
// fill outright — `fill = color_fill.mix(color_fill_focus, focus)` —
// so on the ghost variants (composer tools, the message copy button)
// a solid tint turned the whole button into an accent block with a
// grey icon on it: inverted, not outlined. The token itself must stay
// opaque, because a translucent one would let the page show through
// the solid variants.
color_focus: (RBX_ACCENT_SOFT)

// Disable gradient (color_2) by default
color_2: vec4(-1.0, -1.0, -1.0, -1.0)
Expand Down Expand Up @@ -104,6 +111,9 @@ script_mod! {
color: (COLOR_BG_ACCEPT_GREEN)
color_hover: #D4EED4
color_down: #B8E0B8
// Focus must not repaint a semantic fill: the ring on the border
// carries the signal for this variant.
color_focus: (COLOR_BG_ACCEPT_GREEN)
}
draw_icon.color: (COLOR_FG_ACCEPT_GREEN)
draw_text +: {
Expand All @@ -122,6 +132,9 @@ script_mod! {
color: (COLOR_BG_DANGER_RED)
color_hover: #F0D4D4
color_down: #E0B8B8
// Focus must not repaint a semantic fill: the ring on the border
// carries the signal for this variant.
color_focus: (COLOR_BG_DANGER_RED)
}
draw_icon.color: (COLOR_FG_DANGER_RED)
draw_text +: {
Expand All @@ -140,6 +153,9 @@ script_mod! {
color: (COLOR_SECONDARY)
color_hover: #D0D0D0
color_down: #C0C0C0
// Focus must not repaint a semantic fill: the ring on the border
// carries the signal for this variant.
color_focus: (COLOR_SECONDARY)
}
draw_icon.color: (COLOR_TEXT)
draw_text +: {
Expand All @@ -157,6 +173,8 @@ script_mod! {
color: (RBX_ACCENT)
color_hover: (RBX_ACCENT_HOVER)
color_down: (RBX_ACCENT_PRESSED)
// Solid accent CTA: keep the fill, the ring shows focus.
color_focus: (RBX_ACCENT)
color_disabled: (RBX_BG_DISABLED)
border_color: #0000
border_color_hover: #0000
Expand Down
87 changes: 87 additions & 0 deletions src/shared/room_filter_search_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ script_mod! {
height: 55
flow: Overlay

// Keyboard selection highlight. Driven by an Animator over a shader
// instance rather than `script_apply_eval!`, which does nothing on
// widgets the FlatList builds from a template (pitfall #40).
show_bg: true
draw_bg +: {
selected: instance(0.0)
fn pixel(self) -> vec4 {
return mix(#0000, (RBX_BG_SELECTED), self.selected)
}
}
animator: Animator {
highlight: {
default: @off
off: AnimatorState {
from: {all: Forward {duration: 0.0}}
apply: { draw_bg: {selected: 0.0} }
}
on: AnimatorState {
from: {all: Forward {duration: 0.0}}
apply: { draw_bg: {selected: 1.0} }
}
}
}

row := View {
width: Fill
height: Fill
Expand Down Expand Up @@ -248,10 +272,60 @@ impl RoomFilterSearchResultItem {
pub struct RoomFilterSearchResultsList {
#[deref] view: View,
#[rust] results: Vec<RoomFilterResultTarget>,
/// Which result the arrow keys have landed on. Reset whenever the result set
/// changes, so a stale index cannot survive into a different query.
#[rust] selected_index: usize,
}

impl RoomFilterSearchResultsList {
/// Moves the selection by `delta`, clamped to the ends rather than wrapping:
/// holding an arrow key should come to rest at the first or last result, not
/// cycle past it.
fn move_selection(&mut self, cx: &mut Cx, delta: isize) {
if self.results.is_empty() {
return;
}
let last = self.results.len().saturating_sub(1);
let next = (self.selected_index as isize + delta).clamp(0, last as isize) as usize;
if next != self.selected_index {
self.selected_index = next;
self.view.redraw(cx);
}
}

/// Emits the same action a click on the selected row would.
fn activate_selection(&self, cx: &mut Cx) {
if let Some(target) = self.results.get(self.selected_index) {
cx.action(RoomFilterResultAction::Clicked(target.clone()));
}
}
}

impl Widget for RoomFilterSearchResultsList {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
// Arrow keys and Return are handled here rather than through the text
// input: focus stays in the query field while typing, so the list never
// receives a `Hit` of its own. Only Up/Down/Return are claimed — every
// other key still reaches the field.
if let Event::KeyDown(key) = event {
match key.key_code {
KeyCode::ArrowDown => {
self.move_selection(cx, 1);
return;
}
KeyCode::ArrowUp => {
self.move_selection(cx, -1);
return;
}
KeyCode::ReturnKey | KeyCode::NumpadEnter => {
if !self.results.is_empty() {
self.activate_selection(cx);
return;
}
}
_ => {}
}
}
self.view.handle_event(cx, event, scope);
}

Expand All @@ -265,6 +339,15 @@ impl Widget for RoomFilterSearchResultsList {
for (index, target) in self.results.iter().enumerate() {
let item_id = LiveId(index as u64);
let item = list.item(cx, item_id, id!(result_item)).unwrap();
// Both states are written every frame, never only the selected
// one: these rows are reused by index, so a row left `on` would
// stay highlighted after the selection moved elsewhere.
let item_view = item.as_view();
if index == self.selected_index {
item_view.animator_cut(cx, ids!(highlight.on));
} else {
item_view.animator_cut(cx, ids!(highlight.off));
}
let mut scope = Scope::with_props(target);
item.draw_all(cx, &mut scope);
}
Expand All @@ -277,12 +360,16 @@ impl RoomFilterSearchResultsList {
/// Set the search results to display.
pub fn set_results(&mut self, cx: &mut Cx, results: Vec<RoomFilterResultTarget>) {
self.results = results;
// A new result set invalidates the old position: index 3 of the previous
// query has nothing to do with index 3 of this one.
self.selected_index = 0;
self.view.redraw(cx);
}

/// Clear all search results.
pub fn clear(&mut self, cx: &mut Cx) {
self.results.clear();
self.selected_index = 0;
self.view.redraw(cx);
}

Expand Down
Loading