Add listen search interface
Diff
assets/images/album_art_test.jpg | 3 +++
shalom/src/main.rs | 4 +++-
shalom/src/theme.rs | 2 ++
shalom/src/pages/room.rs | 16 +++++++---------
shalom/src/widgets/mouse_area.rs | 4 ++--
shalom/src/pages/room/listen.rs | 35 +++++++++++++++++++++++++++++------
shalom/src/pages/room/listen/search.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 143 insertions(+), 23 deletions(-)
@@ -1,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4cae52d3c493233ab1681825b15059315572c035937c42937c829e6b359188b8
size 15475
@@ -25,6 +25,7 @@
context_menu: Option<ActiveContextMenu>,
oracle: Option<Arc<Oracle>>,
home_room: Option<&'static str>,
theme: Theme,
}
impl Shalom {
@@ -175,6 +176,7 @@
context_menu: None,
oracle: None,
home_room: Some("living_room"),
theme: Theme::default(),
};
@@ -251,7 +253,7 @@
fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
let page_content = match &self.page {
ActivePage::Loading => Element::from(column!["Loading...",].spacing(20)),
ActivePage::Room(room) => room.view().map(Message::RoomEvent),
ActivePage::Room(room) => room.view(&self.theme).map(Message::RoomEvent),
ActivePage::Omni(omni) => omni.view().map(Message::OmniEvent),
};
@@ -225,6 +225,7 @@
Bedroom,
DiningRoom,
Sunset,
AlbumArtTest,
}
impl Image {
@@ -249,6 +250,7 @@
Image::Bedroom => image!("../../assets/images/bedroom.jpg"),
Image::DiningRoom => image!("../../assets/images/dining_room.jpg"),
Image::Sunset => image!("../../assets/images/sunset-blur.jpg"),
Image::AlbumArtTest => image!("../../assets/images/album_art_test.jpg"),
}
}
@@ -7,8 +7,8 @@
advanced::graphics::core::Element,
font::{Stretch, Weight},
theme,
widget::{container, lazy, row, text, Column},
Color, Font, Length, Renderer, Subscription,
widget::{container, row, text, Column},
Color, Font, Length, Renderer, Subscription, Theme,
};
use crate::{
@@ -58,7 +58,7 @@
}
}
pub fn view(&self) -> Element<'_, Message, Renderer> {
pub fn view(&self, style: &Theme) -> Element<'_, Message, Renderer> {
let header = text(self.room.name.as_ref())
.size(60)
.font(Font {
@@ -69,11 +69,9 @@
.style(theme::Text::Color(Color::WHITE));
let header = if let Page::Listen = self.current_page {
Element::from(lazy(self.room.name.as_ref(), move |_| {
self.listen
.header_magic(header.clone())
.map(Message::Listen)
}))
self.listen
.header_magic(header.clone())
.map(Message::Listen)
} else {
Element::from(header)
};
@@ -84,7 +82,7 @@
col = col.push(match self.current_page {
Page::Climate => Element::from(row![]),
Page::Listen => self.listen.view().map(Message::Listen),
Page::Listen => self.listen.view(style).map(Message::Listen),
Page::Lights => container(self.lights.view().map(Message::Lights))
.padding([0, 40, 0, 40])
.into(),
@@ -43,8 +43,8 @@
impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
#[must_use]
pub fn on_press(mut self, message: Message) -> Self {
self.on_press = Some(message);
pub fn on_press(mut self, message: impl Into<Option<Message>>) -> Self {
self.on_press = message.into();
self
}
@@ -1,10 +1,12 @@
mod search;
use std::{convert::identity, sync::Arc, time::Duration};
use iced::{
futures::StreamExt,
subscription,
widget::{container, image::Handle, Column, Text},
Element, Renderer, Subscription,
widget::{container, image::Handle, lazy, Column, Text},
Element, Length, Renderer, Subscription, Theme,
};
use url::Url;
@@ -47,13 +49,18 @@
}
}
pub fn header_magic<'a>(&self, text: Text<'a>) -> Element<'a, Message> {
header_search(
Message::OnSearchTerm,
Message::OnSearchVisibleChange,
self.search_open,
&self.search_query,
text,
pub fn header_magic(&self, text: Text<'static>) -> Element<'static, Message> {
lazy(
(self.search_open, self.search_query.clone()),
move |(open, query)| {
header_search(
Message::OnSearchTerm,
Message::OnSearchVisibleChange,
*open,
query,
text.clone(),
)
},
)
.into()
}
@@ -159,13 +166,19 @@
}
Message::OnSearchVisibleChange(v) => {
self.search_open = v;
self.search_query = String::new();
None
}
}
}
pub fn view(&self) -> Element<'_, Message, Renderer> {
if let Some((_, speaker)) = self.speaker.clone() {
pub fn view(&self, style: &Theme) -> Element<'_, Message, Renderer> {
if self.search_open && !self.search_query.is_empty() {
container(search::search().view(style))
.padding([0, 40, 40, 40])
.width(Length::Fill)
.into()
} else if let Some((_, speaker)) = self.speaker.clone() {
container(
widgets::media_player::media_player(speaker, self.album_art_image.clone())
.with_artist_logo(
@@ -1,0 +1,102 @@
use iced::{
theme,
widget::{
column, container, container::Appearance, horizontal_rule, image, image::Handle, row,
scrollable, text, Column,
},
Alignment, Background, Color, Element, Length, Renderer, Theme,
};
use crate::{theme::Image, widgets::mouse_area::mouse_area};
pub fn search<M: Clone + 'static>() -> Search<M> {
Search {
on_track_press: None,
}
}
pub struct Search<M> {
on_track_press: Option<fn(String) -> M>,
}
impl<M: Clone + 'static> Search<M> {
pub fn view(&self, style: &Theme) -> Element<'static, M, Renderer> {
let mut col = Column::new();
for i in 0..20 {
if i != 0 {
col = col.push(hr());
}
let track = mouse_area(search_item_container(track_card(
"title",
"artist",
Image::AlbumArtTest,
style,
)))
.on_press(self.on_track_press.map(|f| (f)("hello world".to_string())));
col = col.push(track);
}
search_container(scrollable(col.spacing(10)))
}
}
fn track_card<M: 'static>(
title: &str,
artist: &str,
image_handle: impl Into<Handle>,
style: &Theme,
) -> Element<'static, M, Renderer> {
let title = text(title).style(style.extended_palette().background.base.text);
let artist = text(artist).style(style.extended_palette().background.strong.color);
row![
image(image_handle).width(64).height(64),
column![title, artist,]
]
.align_items(Alignment::Center)
.spacing(10)
.into()
}
fn hr<M: 'static>() -> Element<'static, M, Renderer> {
container(horizontal_rule(1))
.width(Length::Fill)
.padding([10, 0, 10, 0])
.into()
}
fn search_item_container<'a, M: 'a>(
elem: impl Into<Element<'a, M, Renderer>>,
) -> Element<'a, M, Renderer> {
container(elem).padding([0, 20, 0, 20]).into()
}
fn search_container<'a, M: 'a>(
elem: impl Into<Element<'a, M, Renderer>>,
) -> Element<'a, M, Renderer> {
container(elem)
.padding([20, 0, 20, 0])
.width(Length::Fill)
.style(theme::Container::Custom(Box::new(SearchContainer)))
.into()
}
#[allow(clippy::module_name_repetitions)]
pub struct SearchContainer;
impl container::StyleSheet for SearchContainer {
type Style = Theme;
fn appearance(&self, _style: &Self::Style) -> Appearance {
Appearance {
text_color: Some(Color::BLACK),
background: Some(Background::Color(Color::WHITE)),
border_radius: 20.0.into(),
border_width: 0.0,
border_color: Color::default(),
}
}
}