Commit c04ac68a authored by Felix Häcker's avatar Felix Häcker

Implement new StationFavicon struct. It renders favicons by using a...

Implement new StationFavicon struct. It renders favicons by using a GtkDrawingArea to make border radius possible
parent 574f276e
......@@ -1744,6 +1744,7 @@ dependencies = [
name = "shortwave"
version = "0.1.0"
dependencies = [
"cairo-rs 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"diesel 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
......
......@@ -11,6 +11,7 @@ glib = { version = "0.8.1", features = ["subclassing"] }
gobject-sys = "0.9.0"
glib-sys = "0.9.0"
gio = { version = "0.7.0", features = ["v2_46"] }
cairo-rs = "0.7.1"
gdk = "0.11.0"
gdk-pixbuf = { version = "0.7.0", features = ["futures"] }
gstreamer = "0.14.5"
......@@ -27,10 +28,10 @@ mdns = "0.3.1"
serde = "1.0.97"
serde_json = "1.0.40"
serde_derive = "1.0.97"
serde_urlencoded = "0.5.5"
lazy_static = "1.3.0"
matches = "0.1.8"
open = "1.2.3"
serde_urlencoded = "0.5.5"
url = "2.0.0"
diesel = { version = "1.4.2", features = ["sqlite", "r2d2"] }
diesel_migrations = "1.4.0"
......
......@@ -5,6 +5,8 @@
<file compressed="true" preprocess="xml-stripblanks">gtk/player.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/library.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/storefront.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/station_favicon.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/station_dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/station_row.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/station_flowbox.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/menu.ui</file>
......@@ -14,7 +16,6 @@
<file compressed="true" preprocess="xml-stripblanks">gtk/song_row.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/sidebar_controller.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/mini_controller.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/station_dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/tile_button.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/search.ui</file>
......
......@@ -93,9 +93,6 @@
<property name="position">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
......@@ -107,18 +104,14 @@
</packing>
</child>
<child>
<object class="GtkImage" id="favicon_image">
<property name="width_request">46</property>
<property name="height_request">46</property>
<object class="GtkBox" id="favicon_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="pixel_size">24</property>
<property name="icon_name">emblem-music-symbolic</property>
<property name="icon_size">6</property>
<style>
<class name="smallcover"/>
</style>
<property name="halign">center</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
......
......@@ -32,8 +32,6 @@
<object class="GtkSearchEntry" id="search_entry">
<property name="visible">True</property>
<property name="hexpand">True</property>
<property name="margin_left">7</property>
<property name="margin_right">7</property>
</object>
</child>
</object>
......
......@@ -73,17 +73,14 @@
<property name="orientation">vertical</property>
<property name="spacing">3</property>
<child>
<object class="GtkImage" id="favicon_image">
<property name="width_request">192</property>
<property name="height_request">192</property>
<object class="GtkBox" id="favicon_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="pixel_size">128</property>
<property name="icon_name">emblem-music-symbolic</property>
<style>
<class name="cover"/>
</style>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
......
......@@ -2,6 +2,7 @@
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="0.0"/>
<object class="HdyDialog" id="station_dialog">
<property name="width_request">325</property>
<property name="height_request">500</property>
......@@ -103,19 +104,16 @@
<property name="margin_right">12</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="favicon_image">
<property name="width_request">192</property>
<property name="height_request">192</property>
<object class="GtkBox" id="favicon_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="margin_top">24</property>
<property name="margin_bottom">12</property>
<property name="pixel_size">128</property>
<property name="icon_name">emblem-music-symbolic</property>
<style>
<class name="cover"/>
</style>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkBox" id="station_favicon">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="transition_type">crossfade</property>
<child>
<object class="GtkImage" id="placeholder">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="pixel_size">42</property>
<property name="icon_name">emblem-music-symbolic</property>
</object>
<packing>
<property name="name">placeholder</property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
</object>
<packing>
<property name="name">image</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</interface>
......@@ -6,35 +6,20 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkButton" id="button">
<object class="GtkEventBox" id="eventbox">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox">
<property name="width_request">62</property>
<property name="height_request">62</property>
<object class="GtkBox" id="favicon_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="station_favicon">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="pixel_size">32</property>
<property name="icon_name">emblem-music-symbolic</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
<placeholder/>
</child>
</object>
<packing>
......@@ -47,7 +32,6 @@
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
</object>
<packing>
<property name="expand">False</property>
......@@ -61,6 +45,30 @@
<property name="can_focus">False</property>
<property name="border_width">12</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="play_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">media-playback-start-symbolic</property>
</object>
</child>
<style>
<class name="image-button"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
......@@ -110,30 +118,6 @@
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="play_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">media-playback-start-symbolic</property>
</object>
</child>
<style>
<class name="image-button"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
......@@ -143,10 +127,10 @@
</child>
</object>
</child>
<style>
<class name="tile"/>
</style>
</object>
</child>
<style>
<class name="tile"/>
</style>
</object>
</interface>
......@@ -7,33 +7,43 @@
}
.tile {
padding: 0px;
border-radius: 6px;
border: 1px solid @borders;
padding: 0px;
border-radius: 8px;
border: 1px solid @borders;
color: @theme_fg_color;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
background: @theme_base_color;
box-shadow: 0 1px 2px rgba(0,0,0,0.07);
background: @theme_base_color;
}
.tile:hover {
background-color: @theme_base_color;
background-color: @theme_base_color;
}
.tile:backdrop {
box-shadow: inset 0 1px 2px transparent;
}
.cover {
border-radius: 5px;
border: 1px solid @borders;
box-shadow: 0 4px 5px rgba(0,0,0,0.1);
background-color: @theme_base_color;
.favicon-mini {
background: @theme_base_color;
}
.favicon-small {
background: @theme_base_color;
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
.favicon-big {
border-radius: 8px;
border: 1px solid @borders;
box-shadow: 0 4px 5px rgba(0,0,0,0.2);
background-color: @theme_base_color;
}
.smallcover {
background-color: @theme_base_color;
background-color: @theme_base_color;
}
.minicontroller{
border-top: 1px solid @borders;
border-top: 1px solid @borders;
}
......@@ -2,6 +2,7 @@ use soup::prelude::*;
use soup::Session;
use gio::DataInputStream;
use gdk_pixbuf::Pixbuf;
use url::Url;
use crate::api::Error;
use crate::config;
......@@ -22,8 +23,7 @@ impl FaviconDownloader{
Self { session }
}
// TODO: use Url here instead of String
pub async fn download_favicon(self, url: String, size: i32) -> Result<Pixbuf, Error>{
pub async fn download (self, url: Url, size: i32) -> Result<Pixbuf, Error>{
match soup::Message::new("GET", &url.to_string()){
Some(message) => {
// Send created message
......
use url::Url;
use serde::{de, Deserialize, Deserializer};
use std::str::FromStr;
#[derive(Serialize, Deserialize, Debug, Clone, Eq, Hash)]
#[derive(Deserialize, Debug, Clone, Eq, Hash)]
pub struct Station {
#[serde(deserialize_with = "de_from_str")]
#[serde(deserialize_with = "str_to_i32")]
pub id: i32,
pub changeuuid: String,
pub stationuuid: String,
pub name: String,
pub url: String,
pub homepage: String,
pub favicon: String,
#[serde(deserialize_with = "str_to_url")]
pub url: Url,
#[serde(deserialize_with = "str_to_url")]
pub homepage: Url,
#[serde(deserialize_with = "str_to_url")]
pub favicon: Url,
pub tags: String,
pub country: String,
pub state: String,
pub language: String,
#[serde(deserialize_with = "de_from_str")]
#[serde(deserialize_with = "str_to_i32")]
pub votes: i32,
pub negativevotes: String,
pub lastchangetime: String,
......@@ -27,9 +31,9 @@ pub struct Station {
pub lastchecktime: String,
pub lastcheckoktime: String,
pub clicktimestamp: String,
#[serde(deserialize_with = "de_from_str")]
#[serde(deserialize_with = "str_to_i32")]
pub clickcount: i32,
#[serde(deserialize_with = "de_from_str")]
#[serde(deserialize_with = "str_to_i32")]
pub clicktrend: i32,
}
......@@ -39,10 +43,19 @@ impl PartialEq for Station {
}
}
fn de_from_str<'de, D>(deserializer: D) -> Result<i32, D::Error>
fn str_to_i32<'de, D>(deserializer: D) -> Result<i32, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
i32::from_str(&s).map_err(de::Error::custom)
}
fn str_to_url<'de, D>(deserializer: D) -> Result<Url, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Url::from_str(&s).map_err(de::Error::custom)
}
use glib::Sender;
use glib::futures::FutureExt;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
use crate::api::Station;
use crate::api::{Station, FaviconDownloader};
use crate::app::Action;
use crate::audio::Controller;
use crate::audio::PlaybackState;
use crate::ui::{StationFavicon, FaviconSize};
pub struct MiniController {
pub widget: gtk::Box,
sender: Sender<Action>,
station: Rc<RefCell<Option<Station>>>,
station_favicon: Rc<StationFavicon>,
favicon_downloader: FaviconDownloader,
title_label: gtk::Label,
subtitle_label: gtk::Label,
subtitle_revealer: gtk::Revealer,
......@@ -39,10 +44,17 @@ impl MiniController {
let station = Rc::new(RefCell::new(None));
get_widget!(builder, gtk::Box, favicon_box);
let station_favicon = Rc::new(StationFavicon::new(FaviconSize::Mini));
favicon_box.add(&station_favicon.widget);
let favicon_downloader = FaviconDownloader::new();
let controller = Self {
widget: mini_controller,
sender,
station,
station_favicon,
favicon_downloader,
title_label,
subtitle_label,
action_revealer,
......@@ -83,7 +95,15 @@ impl Controller for MiniController {
self.action_revealer.set_reveal_child(true);
self.title_label.set_text(&station.name);
self.title_label.set_tooltip_text(Some(station.name.as_str()));
*self.station.borrow_mut() = Some(station);
*self.station.borrow_mut() = Some(station.clone());
// Download & set icon
let station_favicon = self.station_favicon.clone();
let fut = self.favicon_downloader.clone().download (station.favicon.clone(), FaviconSize::Mini as i32).map(move|pixbuf|{
pixbuf.ok().map(|pixbuf| station_favicon.set_pixbuf(pixbuf));
});
let ctx = glib::MainContext::default();
ctx.spawn_local(fut);
// reset everything else
self.subtitle_revealer.set_reveal_child(false);
......
......@@ -44,7 +44,7 @@ impl MprisController {
let song_title = self.song_title.take();
station.clone().map(|station| {
metadata.art_url = Some(station.favicon);
metadata.art_url = Some(station.favicon.to_string());
metadata.artist = Some(vec![station.name]);
});
song_title.clone().map(|song_title| {
......
use glib::Sender;
use glib::futures::FutureExt;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
use crate::api::Station;
use crate::api::{Station, FaviconDownloader};
use crate::app::Action;
use crate::audio::Controller;
use crate::audio::PlaybackState;
use crate::ui::StationDialog;
use crate::ui::{StationDialog, StationFavicon, FaviconSize};
pub struct SidebarController {
pub widget: gtk::Box,
......@@ -16,6 +17,9 @@ pub struct SidebarController {
station: Rc<RefCell<Option<Station>>>,
app: gtk::Application,
station_favicon: Rc<StationFavicon>,
favicon_downloader: FaviconDownloader,
title_label: gtk::Label,
subtitle_label: gtk::Label,
subtitle_revealer: gtk::Revealer,
......@@ -41,15 +45,21 @@ impl SidebarController {
get_widget!(builder, gtk::Button, info_button);
get_widget!(builder, gtk::Label, error_label);
let station = Rc::new(RefCell::new(None));
let app = builder.get_application().unwrap();
get_widget!(builder, gtk::Box, favicon_box);
let station_favicon = Rc::new(StationFavicon::new(FaviconSize::Big));
favicon_box.add(&station_favicon.widget);
let favicon_downloader = FaviconDownloader::new();
let controller = Self {
widget: sidebar_controller,
sender,
station,
app,
station_favicon,
favicon_downloader,
title_label,
subtitle_label,
action_revealer,
......@@ -97,7 +107,15 @@ impl Controller for SidebarController {
self.action_revealer.set_reveal_child(true);
self.title_label.set_text(&station.name);
self.title_label.set_tooltip_text(Some(station.name.as_str()));
*self.station.borrow_mut() = Some(station);
*self.station.borrow_mut() = Some(station.clone());
// Download & set icon
let station_favicon = self.station_favicon.clone();
let fut = self.favicon_downloader.clone().download (station.favicon.clone(), FaviconSize::Big as i32).map(move|pixbuf|{
pixbuf.ok().map(|pixbuf| station_favicon.set_pixbuf(pixbuf));
});
let ctx = glib::MainContext::default();
ctx.spawn_local(fut);
// reset everything else
self.error_label.set_text(" ");
......
......@@ -76,6 +76,7 @@ sources = files(
'ui/notification.rs',
'ui/song_listbox.rs',
'ui/song_row.rs',
'ui/station_favicon.rs',
'ui/station_dialog.rs',
'ui/station_flowbox.rs',
'ui/station_row.rs',
......
mod notification;
mod song_listbox;
mod song_row;
mod station_favicon;
mod station_dialog;
mod station_flowbox;
mod station_row;
......@@ -9,6 +10,8 @@ mod window;
pub use notification::Notification;
pub use song_listbox::SongListBox;
pub use song_row::SongRow;
pub use station_favicon::FaviconSize;
pub use station_favicon::StationFavicon;
pub use station_dialog::StationDialog;
pub use station_flowbox::StationFlowBox;
pub use station_row::StationRow;
......
use glib::Sender;
use gtk::prelude::*;
use libhandy::Dialog;
use glib::futures::FutureExt;
use crate::api::Station;
use crate::api::{Station, FaviconDownloader};
use crate::app::Action;
use crate::database::Library;
use crate::ui::{StationFavicon, FaviconSize};
pub struct StationDialog {
pub widget: Dialog,
......@@ -32,6 +34,17 @@ impl StationDialog {
get_widget!(builder, gtk::Label, tags_label);
get_widget!(builder, gtk::Label, language_label);
// Download & set station favicon
get_widget!(builder, gtk::Box, favicon_box);
let station_favicon = StationFavicon::new(FaviconSize::Big);
favicon_box.add(&station_favicon.widget);
let favicon_downloader = FaviconDownloader::new();
let fut = favicon_downloader.download(station.favicon.clone(), FaviconSize::Big as i32).map(move|pixbuf|{
pixbuf.ok().map(|pixbuf| station_favicon.set_pixbuf(pixbuf));
});
let ctx = glib::MainContext::default();
ctx.spawn_local(fut);
// Show correct library action
get_widget!(builder, gtk::Stack, library_action_stack);
if Library::contains_station(&station) {
......@@ -64,13 +77,11 @@ impl StationDialog {
self.title_label.set_text(&self.station.name);
let subtitle_text = &format!("{} {} · {} Votes", self.station.country, self.station.state, self.station.votes);
self.subtitle_label.set_text(subtitle_text);
self.homepage_label.set_markup(&format!("<a href=\"{}\">{}</a>", self.station.homepage, self.station.homepage));
if self.station.codec != "" {
self.codec_label.set_text(&self.station.codec);
}
if self.station.homepage != "" {
self.homepage_label.set_markup(&format!("<a href=\"{}\">{}</a>", self.station.homepage, self.station.homepage));
}
if self.station.tags != "" {
self.tags_label.set_text(&self.station.tags);
}
......
use gtk::prelude::*;
use gdk_pixbuf::Pixbuf;
use gdk::ContextExt;
use cairo::Context;
use std::f64;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone, Copy, PartialEq)]
pub enum FaviconSize{
Mini = 46,
Small = 62,
Big = 192,
}
pub struct StationFavicon {
pub widget: gtk::Box,
image: gtk::DrawingArea,
stack: gtk::Stack,
pixbuf: Rc<RefCell<Option<Pixbuf>>>,
size: FaviconSize,
}
impl StationFavicon {
pub fn new(size: FaviconSize) -> Self {
let builder = gtk::Builder::new_from_resource("/de/haeckerfelix/Shortwave/gtk/station_favicon.ui");
get_widget!(builder, gtk::Box, station_favicon);
get_widget!(builder, gtk::DrawingArea, image);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::Image, placeholder);
let pixbuf = Rc::new(RefCell::new(None));
let ctx = station_favicon.get_style_context();
match size{
FaviconSize::Mini => {
ctx.add_class("favicon-mini");
},
FaviconSize::Small => {
ctx.add_class("favicon-small");
},
FaviconSize::Big => {
ctx.add_class("favicon-big");
},
};
image.set_size_request(size as i32, size as i32);
placeholder.set_pixel_size(((size as i32) as f64 * 0.7) as i32);
let favicon = Self {
widget: station_favicon,
image,
stack,
pixbuf,
size,
};
favicon.setup_signals();
favicon
}
pub fn set_pixbuf(&self, pixbuf