efm2/src/efm/efm_icon.c

651 lines
20 KiB
C

#include "efm_icon.h"
#include <Emotion.h>
// XXX: support animated files (anim gif etc.)
// XXX: support video files (mp4 etc.)
// XXX: support edj files ???
// XXX: url's ?
typedef struct _Smart_Data Smart_Data;
struct _Smart_Data
{
Evas_Object_Smart_Clipped_Data __clipped_data;
Eina_Rectangle geom;
Evas_Object *o_smart; // the smart object container itself
Evas_Object *o_image; // the image to be shown right now
Evas_Object *o_image2; // the image being loaded still
Evas_Object *o_video; // the image used for video/audio playback
Ecore_Timer *settle_timer; // time to figure out when
Ecore_Job *wakeup; // a job to wake up the loop
Eina_Stringshare *file; // file path or...
Eina_Stringshare *thumb; // thumb path
Eina_Stringshare *video; // video path
Ecore_Timer *anim_timer; // timer for animation frame flipping
int load_size; // the sie we want to load now
int orig_w, orig_h; // the sie of the img we loaded
int frame; // current frame to display
int frame_count; // number of frames in anim
double video_ratio; // aspect ratio for videos
Evas_Image_Animated_Loop_Hint loop_type; // animated loop type
Eina_Bool alpha : 1; // does the img have alpha
Eina_Bool svg : 1; // is the img a svg
Eina_Bool newfile : 1; // did we just set a new file
Eina_Bool animated : 1; // is this animated?
Eina_Bool vid_stream : 1; // is this video (has video frames?)
Eina_Bool audio : 1; // is this audio (has audo)
Eina_Bool mono_thumb : 1; // is thumb of mono white alpha img
};
static Evas_Smart *_smart = NULL;
static Evas_Smart_Class _sc = EVAS_SMART_CLASS_INIT_NULL;
static Evas_Smart_Class _sc_parent = EVAS_SMART_CLASS_INIT_NULL;
static void _image_add(Smart_Data *sd);
static int _size_choose(Smart_Data *sd, const int *sizes);
static void _cb_wakeup(void *data);
static void _wakeup(Smart_Data *sd);
static void _image_file_set(Smart_Data *sd);
static void _image_thumb_set(Smart_Data *sd);
static void _image_resized(Smart_Data *sd);
static Eina_Bool _cb_settle_timer(void *data);
static void _cb_image_preload(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static void _handle_frame(Smart_Data *sd);
// sizes stored in thumbnail files that we might want to look at
static const int _thumb_sizes[] = { 512, 256, 128, 64, 32, 16, 0 };
// sizes we might want to load/render svg's at
static const int _svg_sizes[] = { 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 0 };
#define ENTRY Smart_Data *sd = evas_object_smart_data_get(obj); if (!sd) return
static void
_image_add(Smart_Data *sd)
{ // add a new hidden image object (image2) that will be busy loading
Evas_Object *o;
if (sd->o_image2) evas_object_del(sd->o_image2);
sd->o_image2 = o = evas_object_image_filled_add
(evas_object_evas_get(sd->o_smart));
evas_object_image_scale_hint_set(o, EVAS_IMAGE_SCALE_HINT_STATIC);
evas_object_smart_member_add(o, sd->o_smart); // this is a member
evas_object_image_load_head_skip_set(o, EINA_TRUE); // fileset no load head
// when the image is fully loaded then call this callback so its all
// ready and no more i/o is happening (this happens in a thread)
evas_object_event_callback_add(o, EVAS_CALLBACK_IMAGE_PRELOADED,
_cb_image_preload, sd);
}
static int
_size_choose(Smart_Data *sd, const int *sizes)
{ // find the next size up we want given the current object size
int i, max_size = 0;
// use the biggest axis as our desired size
if (sd->geom.w > sd->geom.h) max_size = sd->geom.w;
else max_size = sd->geom.h;
for (i = 0; sizes[i] > 0; i++)
{
if (i > 0)
{ // if we're not the first item in the sizes
// if size is bigger than this slot - return the size up
if (max_size > sizes[i]) return sizes[i - 1];
}
else
{ // we're after the first item in our sizes so use biggest
if (max_size > sizes[i]) return sizes[i];
}
}
return sizes[i - 1]; // no size found - choose smallest
}
static void
_cb_wakeup(void *data)
{ // we woke up - mark obj as changed....
Smart_Data *sd = data;
sd->wakeup = NULL;
evas_object_smart_changed(sd->o_smart);
}
static void
_wakeup(Smart_Data *sd)
{ // wake up the main loop so we spin around and do things
if (sd->wakeup) ecore_job_del(sd->wakeup);
sd->wakeup = ecore_job_add(_cb_wakeup, sd);
}
static void
_image_file_set_final(Smart_Data *sd, const char *file, const char *group)
{ // complete image file load/setup, gtrigger preload etc.
const char *ext;
// skip head will make animated not work - so skip it if format might
// be animated so we can flip frames
ext = strrchr(file, '.');
if (ext && ((!strcasecmp(ext, ".gif")) ||
(!strcasecmp(ext, ".webp")) ||
(!strcasecmp(ext, ".avif")) ||
(!strcasecmp(ext, ".avifs")) ||
(!strcasecmp(ext, ".jxl"))))
evas_object_image_load_head_skip_set(sd->o_image2, EINA_FALSE);
evas_object_image_file_set(sd->o_image2, file, group);
evas_object_image_preload(sd->o_image2, EINA_FALSE);
evas_object_smart_changed(sd->o_smart);
_wakeup(sd);
}
static void
_image_file_set(Smart_Data *sd)
{ // set a generic target image file
int load_size = _size_choose(sd, _svg_sizes);
if (sd->o_video)
{ // nuke previous video object if there
evas_object_del(sd->o_video);
sd->o_video = NULL;
}
if (sd->svg)
{ // if it's a svg file...
if (!sd->settle_timer)
{ // if we dont have a pending settle timer anymore use an EXACT sz
if (sd->geom.w > sd->geom.h) load_size = sd->geom.w;
else load_size = sd->geom.h;
}
}
// we already have this thumbnail size so no point loading it again
if (load_size == sd->load_size) return;
_image_add(sd);
sd->load_size = load_size;
if (sd->svg)
{ // it's a svg so tell the svg loader to render at this size
if (sd->load_size == 0) return;
evas_object_image_load_size_set(sd->o_image2,
sd->load_size, sd->load_size);
}
_image_file_set_final(sd, sd->file, NULL);
}
static void
_image_thumb_set(Smart_Data *sd)
{ // set a thumb file from our stored thumbnails
char buf[64];
int load_size = _size_choose(sd, _thumb_sizes);
// we already have this thumbnail size so no point loading it again
if (load_size == sd->load_size) return;
_image_add(sd);
if (sd->newfile)
{
Eet_File *ef = eet_open(sd->thumb, EET_FILE_MODE_READ);
if (ef)
{
int size;
char *mono = eet_read(ef, "image/thumb/mono", &size);
if (mono)
{
sd->mono_thumb = *mono;
free(mono);
}
eet_close(ef);
}
}
sd->load_size = load_size;
snprintf(buf, sizeof(buf), "image/thumb/%i", sd->load_size);
_image_file_set_final(sd, sd->thumb, buf);
}
static void
_cb_vid_frame(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{ // we decoded a frame - make sure vide3o is shown now we have a frame
Smart_Data *sd = data;
evas_object_show(sd->o_video);
}
static void
_cb_vid_resize(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{ // the video changed video size - tell owner we loaded or resized
Smart_Data *sd = data;
emotion_object_size_get(sd->o_video, &(sd->orig_w), &(sd->orig_h));
sd->video_ratio = emotion_object_ratio_get(sd->o_video);
if (sd->newfile)
{ // it's a new file, so say we loaded as its the first time
evas_object_smart_callback_call(sd->o_smart, "loaded", NULL);
sd->newfile = EINA_FALSE;
}
else // already playing the file - not new, si resized
evas_object_smart_callback_call(sd->o_smart, "resized", NULL);
}
static void
_cb_vid_open_done(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{ // we finished opening - now go to 0 and play
Smart_Data *sd = data;
emotion_object_position_set(sd->o_video, 0.0);
emotion_object_play_set(sd->o_video, EINA_TRUE);
}
static void
_cb_vid_play_finish(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{ // we finished playing go back to 0 and restart so we loop
Smart_Data *sd = data;
emotion_object_play_set(sd->o_video, EINA_FALSE);
emotion_object_position_set(sd->o_video, 0.0);
emotion_object_play_set(sd->o_video, EINA_TRUE);
}
static void
_image_video_set(Smart_Data *sd)
{ // set a video file
static Eina_Bool emotion_initted = EINA_FALSE;
Evas_Object *o;
if (sd->o_image)
{ // nuke previous image object if there
evas_object_del(sd->o_image);
sd->o_image = NULL;
}
if (sd->o_image2)
{ // nuke previous image object if there
evas_object_del(sd->o_image2);
sd->o_image2 = NULL;
}
if (sd->o_video)
{ // nuke previous video object if there
evas_object_del(sd->o_video);
sd->o_video = NULL;
}
if (!emotion_initted)
{ // emotion needs an init - so init it first time only
emotion_init();
emotion_initted = EINA_TRUE;
}
// XXX: sometimes this stalls because gstreamer is updating registry data
// in gstreamer_module_init -> gst_init_check -> g_option_context_parse
// -> gst_update_registry -> gst_poll_wait
sd->o_video = o = emotion_object_add(evas_object_evas_get(sd->o_smart));
emotion_object_keep_aspect_set(o, EMOTION_ASPECT_KEEP_NONE);
evas_object_smart_member_add(o, sd->o_smart); // this is a member
evas_object_smart_callback_add(o, "frame_decode", _cb_vid_frame, sd);
evas_object_smart_callback_add(o, "frame_resize", _cb_vid_resize, sd);
evas_object_smart_callback_add(o, "open_done", _cb_vid_open_done, sd);
evas_object_smart_callback_add(o, "playback_finished", _cb_vid_play_finish, sd);
// other callbacks - we don't need these for now
// evas_object_smart_callback_add(o, "decode_stop", _cb_vid_stop, obj);
// evas_object_smart_callback_add(o, "progress_change", _cb_vid_progress, obj);
// evas_object_smart_callback_add(o, "position_update", _cb_position_update, obj);
// evas_object_smart_callback_add(o, "length_change", _cb_length_change, obj);
// evas_object_smart_callback_add(o, "title_change", _cb_title_change, obj);
// evas_object_smart_callback_add(o, "audio_level_change", _cb_audio_change, obj);
// evas_object_smart_callback_add(o, "playback_started", _cb_play_start, obj);
emotion_object_file_set(o, sd->video);
emotion_object_audio_mute_set(o, EINA_TRUE);
emotion_object_audio_volume_set(o, 0.0);
}
static void
_image_resized(Smart_Data *sd)
{ // the image was just resized to handle that settling "idle" timer
if (sd->svg)
{ // it's an svg - so set up a timer so if we dont resize again for 0.2s
if (sd->settle_timer) ecore_timer_reset(sd->settle_timer);
else sd->settle_timer = ecore_timer_add(0.2, _cb_settle_timer, sd);
}
else
{ // not a svg - nuke settle timer from space - the only way to be sure
if (sd->settle_timer)
{
ecore_timer_del(sd->settle_timer);
sd->settle_timer = NULL;
}
}
}
static void
_image_update(Smart_Data *sd)
{ // update the image - perhaps size changed...
if (sd->thumb) _image_thumb_set(sd);
else if (sd->file) _image_file_set(sd);
}
static Eina_Bool
_cb_settle_timer(void *data)
{ // when the image settles and stops being resized after a bit...
Smart_Data *sd = data;
sd->settle_timer = NULL;
_image_update(sd);
return EINA_FALSE;
}
static int
_frame_num_get(Smart_Data *sd)
{
int fr, fr2;
// ping pong - first and last frame only appear once at each end
if (sd->loop_type == EVAS_IMAGE_ANIMATED_HINT_PINGPONG)
{
fr = sd->frame % ((sd->frame_count * 2) - 2);
fr2 = sd->frame % sd->frame_count + 1;
if (fr >= sd->frame_count) fr = sd->frame_count - 1 - fr2;
}
// loop
else fr = sd->frame % sd->frame_count;
return fr;
}
static Eina_Bool
_cb_anim_timer(void *data)
{
Smart_Data *sd = data;
int fr;
sd->anim_timer = NULL;
sd->frame++;
fr = _frame_num_get(sd);
printf("T: %1.3f %i / %i\n", ecore_time_get(), fr, sd->frame_count);
evas_object_image_animated_frame_set(sd->o_image, fr);
_handle_frame(sd);
return EINA_FALSE;
}
static void
_handle_frame(Smart_Data *sd)
{
int fr;
double t;
fr = _frame_num_get(sd);
t = evas_object_image_animated_frame_duration_get(sd->o_image, fr, 0);
if (sd->anim_timer) ecore_timer_del(sd->anim_timer);
sd->anim_timer = ecore_timer_add(t, _cb_anim_timer, sd);
}
static void
_cb_image_preload(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{ // whenb an image that was being loaded in the background has now finished
Smart_Data *sd = data;
if (sd->o_image) evas_object_del(sd->o_image);
sd->o_image = sd->o_image2;
if (!sd->o_image2) return;
sd->o_image2 = NULL;
evas_object_image_size_get(sd->o_image, &(sd->orig_w), &(sd->orig_h));
sd->alpha = evas_object_image_alpha_get(sd->o_image);
evas_object_show(sd->o_image);
if (sd->newfile)
{
sd->newfile = EINA_FALSE;
evas_object_smart_callback_call(sd->o_smart, "loaded", NULL);
}
sd->animated = evas_object_image_animated_get(sd->o_image);
if (sd->animated)
{
sd->loop_type = evas_object_image_animated_loop_type_get(sd->o_image);
sd->frame_count = evas_object_image_animated_frame_count_get(sd->o_image);
if (sd->frame_count < 3) sd->loop_type = EVAS_IMAGE_ANIMATED_HINT_LOOP;
else if (sd->frame_count < 2) sd->animated = EINA_FALSE;
if (sd->animated) _handle_frame(sd);
}
}
// gui code
static void
_smart_add(Evas_Object *obj)
{ // create a new efm icon
Smart_Data *sd;
sd = calloc(1, sizeof(Smart_Data));
if (!sd) return;
evas_object_smart_data_set(obj, sd);
_sc_parent.add(obj);
sd->o_smart = obj;
}
static void
_smart_del(Evas_Object *obj)
{ // delete/free efm view
ENTRY;
if (sd->o_image)
{
evas_object_del(sd->o_image);
sd->o_image = NULL;
}
if (sd->o_image2)
{
evas_object_del(sd->o_image2);
sd->o_image2 = NULL;
}
if (sd->o_video)
{
evas_object_del(sd->o_video);
sd->o_video = NULL;
}
if (sd->settle_timer)
{
ecore_timer_del(sd->settle_timer);
sd->settle_timer = NULL;
}
if (sd->wakeup)
{
ecore_job_del(sd->wakeup);
sd->wakeup = NULL;
}
if (sd->anim_timer)
{
ecore_timer_del(sd->anim_timer);
sd->anim_timer = NULL;
}
eina_stringshare_replace(&(sd->thumb), NULL);
eina_stringshare_replace(&(sd->file), NULL);
eina_stringshare_replace(&(sd->video), NULL);
_sc_parent.del(obj);
evas_object_smart_data_set(obj, NULL);
}
static void
_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{ // efm icon object moved
ENTRY;
if ((sd->geom.x == x) && (sd->geom.y == y)) return;
sd->geom.x = x;
sd->geom.y = y;
evas_object_smart_changed(obj);
}
static void
_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{ // efm icon object resized
ENTRY;
if ((sd->geom.w == w) && (sd->geom.h == h)) return;
sd->geom.w = w;
sd->geom.h = h;
_image_resized(sd);
_image_update(sd);
evas_object_smart_changed(obj);
}
static void
_smart_calculate(Evas_Object *obj)
{ // recalce position/size
ENTRY;
if (sd->o_image)
evas_object_geometry_set(sd->o_image,
sd->geom.x, sd->geom.y,
sd->geom.w, sd->geom.h);
if (sd->o_image2)
evas_object_geometry_set(sd->o_image2,
sd->geom.x, sd->geom.y,
sd->geom.w, sd->geom.h);
if (sd->o_video)
evas_object_geometry_set(sd->o_video,
sd->geom.x, sd->geom.y,
sd->geom.w, sd->geom.h);
_image_update(sd);
}
Evas_Object *
efm_icon_add(Evas_Object *parent)
{ // add new icon object
if (!_smart)
{
evas_object_smart_clipped_smart_set(&_sc_parent);
_sc = _sc_parent;
_sc.name = "efm_icon";
_sc.version = EVAS_SMART_CLASS_VERSION;
_sc.add = _smart_add;
_sc.del = _smart_del;
_sc.resize = _smart_resize;
_sc.move = _smart_move;
_sc.calculate = _smart_calculate;
};
if (!_smart) _smart = evas_smart_class_new(&_sc);
return evas_object_smart_add(evas_object_evas_get(parent), _smart);
}
void
efm_icon_file_set(Evas_Object *obj, const char *file)
{ // set a regular file as the icon
ENTRY;
if ((sd->file) && (file) && (!strcmp(sd->file, file))) return;
if ((!sd->file) && (!file)) return;
eina_stringshare_replace(&(sd->thumb), NULL);
eina_stringshare_replace(&(sd->file), file);
eina_stringshare_replace(&(sd->video), NULL);
sd->svg = EINA_FALSE;
if ((sd->file) &&
((eina_fnmatch("*.svg", sd->file, EINA_FNMATCH_CASEFOLD) ||
eina_fnmatch("*.svgz", sd->file, EINA_FNMATCH_CASEFOLD) ||
eina_fnmatch("*.svg.gz", sd->file, EINA_FNMATCH_CASEFOLD))))
sd->svg = EINA_TRUE;
sd->load_size = -1;
sd->newfile = EINA_TRUE;
sd->mono_thumb = EINA_FALSE;
sd->frame = 0;
if (sd->anim_timer)
{
ecore_timer_del(sd->anim_timer);
sd->anim_timer = NULL;
}
if (sd->settle_timer)
{ // thumbnails dont need settle timers like svg's might
ecore_timer_del(sd->settle_timer);
sd->settle_timer = NULL;
}
_image_resized(sd);
_image_file_set(sd);
}
void
efm_icon_thumb_set(Evas_Object *obj, const char *thumb)
{ // specifically add generated thumb file
ENTRY;
if ((sd->thumb) && (thumb) && (!strcmp(sd->thumb, thumb))) return;
if ((!sd->thumb) && (!thumb)) return;
eina_stringshare_replace(&(sd->file), NULL);
eina_stringshare_replace(&(sd->thumb), thumb);
eina_stringshare_replace(&(sd->video), NULL);
sd->svg = EINA_FALSE;
sd->load_size = -1;
sd->newfile = EINA_TRUE;
sd->mono_thumb = EINA_FALSE;
sd->frame = 0;
if (sd->anim_timer)
{
ecore_timer_del(sd->anim_timer);
sd->anim_timer = NULL;
}
if (sd->settle_timer)
{ // thumbnails dont need settle timers like svg's might
ecore_timer_del(sd->settle_timer);
sd->settle_timer = NULL;
}
_image_thumb_set(sd);
}
void
efm_icon_video_set(Evas_Object *obj, const char *video)
{ // specifically load file as a video (or audio) and not as a still image
ENTRY;
if ((sd->video) && (video) && (!strcmp(sd->video, video))) return;
if ((!sd->video) && (!video)) return;
eina_stringshare_replace(&(sd->file), NULL);
eina_stringshare_replace(&(sd->thumb), NULL);
eina_stringshare_replace(&(sd->video), video);
sd->svg = EINA_FALSE;
sd->load_size = -1;
sd->newfile = EINA_TRUE;
sd->mono_thumb = EINA_FALSE;
sd->frame = 0;
sd->video_ratio = 0.0;
if (sd->anim_timer)
{
ecore_timer_del(sd->anim_timer);
sd->anim_timer = NULL;
}
if (sd->settle_timer)
{ // thumbnails dont need settle timers like svg's might
ecore_timer_del(sd->settle_timer);
sd->settle_timer = NULL;
}
_image_resized(sd);
_image_video_set(sd);
}
void
efm_icon_size_get(Evas_Object *obj, int *w, int *h)
{ // get image pixel size
ENTRY;
if ((sd->o_video) && (sd->video_ratio > 0.0))
{
int vid_w, vid_h;
vid_w = ((double)sd->orig_h + 0.5) * sd->video_ratio;
vid_h = sd->orig_h;
if (w) *w = vid_w;
if (h) *h = vid_h;
}
else
{
if (w) *w = sd->orig_w;
if (h) *h = sd->orig_h;
}
}
Eina_Bool
efm_icon_alpha_get(Evas_Object *obj)
{ // get image alpha flag
ENTRY EINA_FALSE;
return sd->alpha;
}
Eina_Bool
efm_icon_mono_get(Evas_Object *obj)
{ // get thumb mono flag
ENTRY EINA_FALSE;
return sd->mono_thumb;
}