Commit 790f2898 authored by Daniel García Moreno's avatar Daniel García Moreno

New image widget to render images async

parent 4b45f1c8
......@@ -29,8 +29,7 @@ use backend::BKCommand;
use types::StickerGroup;
use types::Sticker;
use types::Message;
use util::{load_async, Thumb};
use widgets;
impl AppOp {
......@@ -160,9 +159,10 @@ impl AppOp {
content.pack_start(&bx, true, true, 6);
}
let image = gtk::Image::new();
let backend = self.backend.clone();
let image = widgets::image::Image::new(&backend, &img.thumbnail.clone(), (size, size), widgets::image::Thumb(true));
let eb = gtk::EventBox::new();
eb.add(&image);
eb.add(&image.widget);
bx.add(&eb);
let internal = self.internal.clone();
......@@ -176,10 +176,6 @@ impl AppOp {
internal.send(command).unwrap();
glib::signal::Inhibit(false)
});
let backend = self.backend.clone();
let (w, h) = img.size;
load_async(&backend, &img.thumbnail.clone(), &image, (size, h * size / w), Thumb(true));
}
content.show_all();
......
extern crate glib;
extern crate cairo;
extern crate gtk;
extern crate gdk;
extern crate gdk_pixbuf;
extern crate tree_magic;
use self::gtk::ImageExt;
use self::gtk::IconThemeExt;
use self::gtk::WidgetExt;
use std::sync::mpsc::channel;
use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc::TryRecvError;
use std::path::Path;
use backend::BKCommand;
use self::gdk_pixbuf::Pixbuf;
use self::gdk_pixbuf::PixbufExt;
use self::gdk_pixbuf::PixbufAnimation;
use self::gdk_pixbuf::PixbufAnimationExt;
use failure::Error;
use self::gdk::ContextExt;
......@@ -72,111 +59,3 @@ pub fn get_pixbuf_data(pb: &Pixbuf) -> Result<Vec<u8>, Error> {
pub fn markup_text(s: &str) -> String {
markup_links(&html_escape(s))
}
pub struct Thumb(pub bool);
/// If `path` starts with mxc this func download the img async, in other case the image is loaded
/// in the `image` widget scaled to size
pub fn load_async(backend: &Sender<BKCommand>,
path: &str,
img: &gtk::Image,
size: (i32, i32),
Thumb(thumb): Thumb) {
if path.starts_with("mxc:") {
let pixbuf = match gtk::IconTheme::get_default() {
None => None,
Some(i1) => match i1.load_icon("image-loading-symbolic", size.0, gtk::IconLookupFlags::empty()) {
Err(_) => None,
Ok(i2) => i2,
}
};
if let Some(pix) = pixbuf {
img.set_from_pixbuf(&pix);
}
// asyn load
let (tx, rx): (Sender<String>, Receiver<String>) = channel();
let command = match thumb {
false => BKCommand::GetMediaAsync(path.to_string(), tx),
true => BKCommand::GetThumbAsync(path.to_string(), tx),
};
backend.send(command).unwrap();
let im = img.clone();
gtk::timeout_add(50, move || match rx.try_recv() {
Err(TryRecvError::Empty) => gtk::Continue(true),
Err(TryRecvError::Disconnected) => gtk::Continue(false),
Ok(fname) => {
load_pixbuf(&fname, &im, size);
gtk::Continue(false)
}
});
} else {
load_pixbuf(path, &img, size);
}
}
pub fn is_gif(fname: &str) -> bool {
let result = tree_magic::from_filepath(&Path::new(fname));
result == "image/gif"
}
pub fn load_pixbuf(fname: &str, image: &gtk::Image, size: (i32, i32)) {
if is_gif(&fname) {
load_animation(&fname, &image, size);
return;
}
let pixbuf = Pixbuf::new_from_file(fname);
if let Ok(pix) = pixbuf {
if let Err(_) = draw_pixbuf(&pix, image, size) {
image.set_from_file(fname);
}
} else {
image.set_from_file(fname);
}
}
pub fn draw_pixbuf(pix: &Pixbuf, image: &gtk::Image, size: (i32, i32)) -> Result<(), ()> {
let mut w = pix.get_width();
let mut h = pix.get_height();
if w > h && w > size.0 {
h = size.1 * h / w;
w = size.0;
} else if h >= w && h > size.1 {
w = size.0 * w / h;
h = size.1;
}
if let Some(scaled) = pix.scale_simple(w, h, gdk_pixbuf::InterpType::Bilinear) {
image.set_from_pixbuf(&scaled);
return Ok(())
}
Err(())
}
pub fn load_animation(fname: &str, image: &gtk::Image, size: (i32, i32)) {
let res = PixbufAnimation::new_from_file(fname);
if res.is_err() {
return;
}
let anim = res.unwrap();
let iter = anim.get_iter(&glib::get_current_time());
let im = image.clone();
gtk::timeout_add(iter.get_delay_time() as u32, move || {
iter.advance(&glib::get_current_time());
if im.is_drawable() {
let pix = iter.get_pixbuf();
let _ = draw_pixbuf(&pix, &im, size);
} else {
if let None = im.get_parent() {
return gtk::Continue(false);
}
}
gtk::Continue(true)
});
}
extern crate gtk;
extern crate glib;
extern crate gdk;
extern crate gdk_pixbuf;
extern crate tree_magic;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::channel;
use std::sync::mpsc::{Sender, Receiver};
use self::gtk::prelude::*;
use self::gtk::DrawingArea;
use self::gdk_pixbuf::Pixbuf;
use self::gdk_pixbuf::PixbufExt;
use self::gdk::ContextExt;
use self::gdk_pixbuf::PixbufAnimation;
use self::gdk_pixbuf::PixbufAnimationExt;
use backend::BKCommand;
use std::sync::mpsc::TryRecvError;
pub struct Thumb(pub bool);
#[derive(Clone, Debug)]
pub struct Image {
pub path: String,
pub max_size: (i32, i32),
pub widget: DrawingArea,
pub backend: Sender<BKCommand>,
pub pixbuf: Arc<Mutex<Option<Pixbuf>>>,
pub thumb: bool,
}
impl Image {
pub fn new(backend: &Sender<BKCommand>, path: &str, size: (i32, i32), Thumb(thumb): Thumb) -> Image {
let da = DrawingArea::new();
let pixbuf = match gtk::IconTheme::get_default() {
None => None,
Some(i1) => match i1.load_icon("image-loading-symbolic", size.1, gtk::IconLookupFlags::empty()) {
Err(_) => None,
Ok(i2) => i2,
}
};
let img = Image {
path: path.to_string(),
max_size: size,
widget: da,
pixbuf: Arc::new(Mutex::new(pixbuf)),
thumb: thumb,
backend: backend.clone(),
};
img.draw();
img.load_async();
img
}
pub fn draw(&self) {
let da = &self.widget;
let w = self.max_size.0;
let h = self.max_size.1;
da.set_hexpand(true);
da.set_vexpand(false);
if let Some(ref pb) = *self.pixbuf.lock().unwrap() {
let w = pb.get_width();
let h = pb.get_height();
da.set_size_request(w, h);
} else {
da.set_size_request(w, h);
}
let pix = self.pixbuf.clone();
da.connect_draw(move |da, g| {
let width = w as f64;
let height = h as f64;
let mut rw = w;
if let Some(p) = da.get_parent() {
let parent_width = p.get_allocated_width();
if parent_width < w {
rw = parent_width;
}
}
let context = da.get_style_context().unwrap();
gtk::render_background(&context, g, 0.0, 0.0, width, height);
if let Some(ref pb) = *pix.lock().unwrap() {
let mut pw = pb.get_width();
let mut ph = pb.get_height();
if pw > ph && pw > rw {
ph = rw * ph / pw;
pw = rw;
} else if ph >= pw && ph > h {
pw = h * pw / ph;
ph = h;
}
da.set_size_request(pw, ph);
if let Some(scaled) = pb.scale_simple(pw, ph, gdk_pixbuf::InterpType::Bilinear) {
g.set_source_pixbuf(&scaled, 0.0, 0.0);
g.rectangle(0.0, 0.0, pw as f64, ph as f64);
g.fill();
}
}
Inhibit(false)
});
}
/// If `path` starts with mxc this func download the img async, in other case the image is loaded
/// in the `image` widget scaled to size
pub fn load_async(&self) {
if self.path.starts_with("mxc:") {
// asyn load
let (tx, rx): (Sender<String>, Receiver<String>) = channel();
let command = match self.thumb {
false => BKCommand::GetMediaAsync(self.path.to_string(), tx),
true => BKCommand::GetThumbAsync(self.path.to_string(), tx),
};
self.backend.send(command).unwrap();
let pix = self.pixbuf.clone();
let da = self.widget.clone();
gtk::timeout_add(50, move || match rx.try_recv() {
Err(TryRecvError::Empty) => gtk::Continue(true),
Err(TryRecvError::Disconnected) => gtk::Continue(false),
Ok(fname) => {
load_pixbuf(pix.clone(), da.clone(), &fname);
gtk::Continue(false)
}
});
} else {
load_pixbuf(self.pixbuf.clone(), self.widget.clone(), &self.path);
}
}
}
pub fn load_pixbuf(pix: Arc<Mutex<Option<Pixbuf>>>, widget: DrawingArea, fname: &str) {
if is_gif(&fname) {
load_animation(pix.clone(), widget, &fname);
return;
}
match Pixbuf::new_from_file(fname) {
Ok(px) => { *pix.lock().unwrap() = Some(px); }
_ => { *pix.lock().unwrap() = None; }
};
}
pub fn load_animation(pix: Arc<Mutex<Option<Pixbuf>>>, widget: DrawingArea, fname: &str) {
let res = PixbufAnimation::new_from_file(fname);
if res.is_err() {
return;
}
let anim = res.unwrap();
let iter = anim.get_iter(&glib::get_current_time());
gtk::timeout_add(iter.get_delay_time() as u32, move || {
iter.advance(&glib::get_current_time());
if widget.is_drawable() {
let px = iter.get_pixbuf();
*pix.lock().unwrap() = Some(px);
widget.queue_draw();
} else {
return gtk::Continue(false);
}
gtk::Continue(true)
});
}
pub fn is_gif(fname: &str) -> bool {
let p = &Path::new(fname);
if !p.is_file() {
return false;
}
let result = tree_magic::from_filepath(p);
result == "image/gif"
}
......@@ -14,7 +14,6 @@ use backend::BKCommand;
use fractal_api as api;
use util::markup_text;
use util::{load_async, Thumb};
use std::path::Path;
......@@ -260,13 +259,12 @@ impl<'a> MessageBox<'a> {
fn build_room_msg_image(&self) -> gtk::Box {
let msg = self.msg;
let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
let image = gtk::Image::new();
let viewbtn = gtk::Button::new();
viewbtn.set_relief(gtk::ReliefStyle::None);
let url = msg.url.clone().unwrap_or_default();
let backend = self.op.backend.clone();
load_async(&backend, &msg.thumb.clone().unwrap_or_default(), &image, (600, 400), Thumb(true));
let image = widgets::image::Image::new(&backend, &msg.thumb.clone().unwrap_or_default(), (600, 400), widgets::image::Thumb(false));
//let img = image.clone();
viewbtn.connect_clicked(move |_| {
......@@ -276,7 +274,7 @@ impl<'a> MessageBox<'a> {
backend.send(BKCommand::GetMedia(url.clone())).unwrap();
});
viewbtn.set_image(&image);
viewbtn.set_image(&image.widget);
bx.add(&viewbtn);
bx
......@@ -285,13 +283,12 @@ impl<'a> MessageBox<'a> {
fn build_room_msg_sticker(&self) -> gtk::Box {
let msg = self.msg;
let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
let image = gtk::Image::new();
let backend = self.op.backend.clone();
load_async(&backend, &msg.url.clone().unwrap_or_default(), &image, (600, 400), Thumb(false));
image.set_tooltip_text(&self.msg.body[..]);
let image = widgets::image::Image::new(&backend, &msg.url.clone().unwrap_or_default(), (600, 400), widgets::image::Thumb(false));
let w = image.widget.clone();
w.set_tooltip_text(&self.msg.body[..]);
bx.add(&image);
bx.add(&w);
bx
}
......
......@@ -6,6 +6,7 @@ mod roomlist;
mod avatar;
mod autocomplete;
pub mod divider;
pub mod image;
pub use self::message::MessageBox;
pub use self::room::RoomBox;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment