Search Entry City Lookup and Suggestion Popover
The search entry in the Weather App allows users to search for cities dynamically.
Instead of requiring users to type the full city name correctly, the application provides live suggestions using the Open-Meteo geocoding API. This improves usability and reduces input errors. In the sidebar, the search entry is placed together with the add city button. This means the top interaction area supports both:
- Searching for cities
- Adding a city explicitly
This keeps the interface compact and easy to understand.
Creating the Search Entry
The Weather App creates the search field like this:
#![allow(unused)] fn main() { let search = gtk::SearchEntry::new(); search.set_hexpand(true); search.set_placeholder_text(Some("Search city…")); }
SearchEntry is used instead of a normal Entry because:
- It is visually optimized for search tasks
- It includes built-in styling
- It integrates well with GNOME design patterns
The field expands horizontally to fill available space inside the sidebar.
Adding the Add City Button
Next to the search field, the Weather App creates an add button:
#![allow(unused)] fn main() { let add_city_btn = gtk::Button::from_icon_name("list-add-symbolic"); }
This button gives users a direct way to add a city.
Using an icon button keeps the interface minimal and matches GNOME design conventions. The plus symbol clearly communicates the purpose of the action.
Combining Search Entry and Add Button in One Row
The search entry and add button are placed together inside a horizontal layout:
#![allow(unused)] fn main() { let search_row = gtk::Box::new(gtk::Orientation::Horizontal, 8); search_row.append(&search); search_row.append(&add_city_btn); }
This creates a single interaction row at the top of the sidebar.
The order of the append() calls is important:
search_row.append(&search);places the search field firstsearch_row.append(&add_city_btn);places the add button next to it
So the layout appears like this:
[ SearchEntry | Add Button ]
This arrangement improves usability because:
- Search and add actions are closely related
- Users can immediately see where to type
- Users can also add a city directly from the same row
The search field expands, while the add button remains compact.
Suggestion Popover Structure
When the user types, suggestions appear in a popover.
The structure is:
#![allow(unused)] fn main() { let suggestion_list = gtk::ListBox::new(); let suggestion_popover = gtk::Popover::new(); suggestion_popover.set_parent(&search); suggestion_popover.set_child(Some(&suggestion_list)); }
This creates:
- A floating popover
- Attached directly to the search field
- Containing a list of suggestion rows
This avoids navigating away from the sidebar.
The suggestion UI remains lightweight and contextual.
Reacting to User Input
The Weather App listens to changes in the search field:
#![allow(unused)] fn main() { search.connect_search_changed(move |s| { let q = s.text().to_string(); }); }
Whenever the text changes:
- The current query is captured
- If the text is too short, suggestions are hidden
- Otherwise, a background request is triggered
This prevents unnecessary API calls.
Fetching Suggestions (Background Thread)
To avoid freezing the UI, the API call runs in a separate thread:
#![allow(unused)] fn main() { thread::spawn(move || { let res = fetch_geocoding_suggestions(&q_clone, 5); }); }
This ensures:
- The UI remains responsive
- The search field does not lag
- Users can continue typing
GTK’s main thread is never blocked.
Updating the UI Safely
After receiving suggestions, the UI is updated using a timeout polling mechanism:
#![allow(unused)] fn main() { glib::timeout_add_local(Duration::from_millis(80), move || { match rx.try_recv() { ... } }); }
This allows:
- Safe UI updates from the main thread
- Controlled checking of background results
- Smooth suggestion appearance
This design respects GTK’s single-threaded UI model.
Displaying Suggestions
When results are returned, the list is rebuilt:
#![allow(unused)] fn main() { clear_listbox(&suggestion_list); for item in items { let row = ActionRow::new(); row.set_title(&item.name); suggestion_list.append(&row); } }
Each suggestion:
- Is displayed as an
ActionRow - Can be clicked
- Updates the search field
- Triggers weather fetch
This keeps the interaction intuitive.
Handling No Results
If no city is found:
#![allow(unused)] fn main() { row.set_title("No city found"); row.set_subtitle("Try another spelling"); }
This avoids silent failure.
The user receives immediate feedback.
Why This Design Is Important
This search system demonstrates:
- Real-time UI interaction
- Safe multi-threading in GTK
- Controlled UI updates
- Clean separation between network logic and UI logic
From an onboarding perspective, it teaches:
- Event-driven programming
- Background processing in Rust
- GTK-safe UI updates
- User-friendly feedback handling
Architectural Flow
Search workflow in the Weather App:
- User types into SearchEntry
search_changedsignal triggers- Background thread calls geocoding API
- Main thread updates suggestion popover
- User selects a suggestion
- Weather fetch begins
This flow is modular and easy to follow.