Add ability to keep zoom/position when browsing

This commit is contained in:
Přemysl Eric Janouch 2022-07-17 12:37:32 +02:00
parent 9a0647fdfd
commit bd2e929b77
Signed by: p
GPG Key ID: A0420B94F92B9493
6 changed files with 189 additions and 4 deletions

View File

@ -14,6 +14,8 @@ Features
- Employs high-performance file format libraries: Wuffs and libjpeg-turbo. - Employs high-performance file format libraries: Wuffs and libjpeg-turbo.
- Makes use of 30-bit X.org visuals, whenever it's possible and appropriate. - Makes use of 30-bit X.org visuals, whenever it's possible and appropriate.
- Has a notion of pages, and tries to load all included content within files. - Has a notion of pages, and tries to load all included content within files.
- Can keep the zoom and position when browsing, to help with comparing
zoomed-in images.
Explicit non-goals Explicit non-goals
------------------ ------------------

View File

@ -67,6 +67,7 @@ struct _FivView {
bool checkerboard : 1; ///< Show checkerboard background bool checkerboard : 1; ///< Show checkerboard background
bool enhance : 1; ///< Try to enhance picture data bool enhance : 1; ///< Try to enhance picture data
bool scale_to_fit : 1; ///< Image no larger than the allocation bool scale_to_fit : 1; ///< Image no larger than the allocation
bool fixate : 1; ///< Keep zoom and position
double scale; ///< Scaling factor double scale; ///< Scaling factor
double drag_start[2]; ///< Adjustment values for drag origin double drag_start[2]; ///< Adjustment values for drag origin
@ -125,6 +126,7 @@ enum {
PROP_MESSAGES = 1, PROP_MESSAGES = 1,
PROP_SCALE, PROP_SCALE,
PROP_SCALE_TO_FIT, PROP_SCALE_TO_FIT,
PROP_FIXATE,
PROP_ENABLE_CMS, PROP_ENABLE_CMS,
PROP_FILTER, PROP_FILTER,
PROP_CHECKERBOARD, PROP_CHECKERBOARD,
@ -254,6 +256,9 @@ fiv_view_get_property(
case PROP_SCALE_TO_FIT: case PROP_SCALE_TO_FIT:
g_value_set_boolean(value, self->scale_to_fit); g_value_set_boolean(value, self->scale_to_fit);
break; break;
case PROP_FIXATE:
g_value_set_boolean(value, self->fixate);
break;
case PROP_ENABLE_CMS: case PROP_ENABLE_CMS:
g_value_set_boolean(value, self->enable_cms); g_value_set_boolean(value, self->enable_cms);
break; break;
@ -311,6 +316,10 @@ fiv_view_set_property(
if (self->scale_to_fit != g_value_get_boolean(value)) if (self->scale_to_fit != g_value_get_boolean(value))
fiv_view_command(self, FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT); fiv_view_command(self, FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT);
break; break;
case PROP_FIXATE:
if (self->fixate != g_value_get_boolean(value))
fiv_view_command(self, FIV_VIEW_COMMAND_TOGGLE_FIXATE);
break;
case PROP_ENABLE_CMS: case PROP_ENABLE_CMS:
if (self->enable_cms != g_value_get_boolean(value)) if (self->enable_cms != g_value_get_boolean(value))
fiv_view_command(self, FIV_VIEW_COMMAND_TOGGLE_CMS); fiv_view_command(self, FIV_VIEW_COMMAND_TOGGLE_CMS);
@ -636,7 +645,12 @@ static gboolean
set_scale_to_fit(FivView *self, bool scale_to_fit) set_scale_to_fit(FivView *self, bool scale_to_fit)
{ {
if (self->scale_to_fit != scale_to_fit) { if (self->scale_to_fit != scale_to_fit) {
self->scale_to_fit = scale_to_fit; if ((self->scale_to_fit = scale_to_fit)) {
self->fixate = false;
g_object_notify_by_pspec(
G_OBJECT(self), view_properties[PROP_FIXATE]);
}
g_object_notify_by_pspec( g_object_notify_by_pspec(
G_OBJECT(self), view_properties[PROP_SCALE_TO_FIT]); G_OBJECT(self), view_properties[PROP_SCALE_TO_FIT]);
gtk_widget_queue_resize(GTK_WIDGET(self)); gtk_widget_queue_resize(GTK_WIDGET(self));
@ -1270,6 +1284,9 @@ fiv_view_class_init(FivViewClass *klass)
view_properties[PROP_SCALE_TO_FIT] = g_param_spec_boolean( view_properties[PROP_SCALE_TO_FIT] = g_param_spec_boolean(
"scale-to-fit", "Scale to fit", "Scale images down to fit the window", "scale-to-fit", "Scale to fit", "Scale images down to fit the window",
TRUE, G_PARAM_READWRITE); TRUE, G_PARAM_READWRITE);
view_properties[PROP_FIXATE] = g_param_spec_boolean(
"fixate", "Fixate", "Keep zoom and position",
FALSE, G_PARAM_READWRITE);
view_properties[PROP_ENABLE_CMS] = g_param_spec_boolean( view_properties[PROP_ENABLE_CMS] = g_param_spec_boolean(
"enable-cms", "Enable CMS", "Enable color management", "enable-cms", "Enable CMS", "Enable color management",
TRUE, G_PARAM_READWRITE); TRUE, G_PARAM_READWRITE);
@ -1443,6 +1460,9 @@ fiv_view_set_uri(FivView *self, const char *uri)
self->frame = self->page = NULL; self->frame = self->page = NULL;
self->image = surface; self->image = surface;
switch_page(self, self->image); switch_page(self, self->image);
// Otherwise, adjustment values and zoom are retained implicitly.
if (!self->fixate)
set_scale_to_fit(self, true); set_scale_to_fit(self, true);
g_free(self->uri); g_free(self->uri);
@ -1592,5 +1612,10 @@ fiv_view_command(FivView *self, FivViewCommand command)
set_scale_to_fit_height(self); set_scale_to_fit_height(self);
break; case FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT: break; case FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT:
set_scale_to_fit(self, !self->scale_to_fit); set_scale_to_fit(self, !self->scale_to_fit);
break; case FIV_VIEW_COMMAND_TOGGLE_FIXATE:
if ((self->fixate = !self->fixate))
set_scale_to_fit(self, false);
g_object_notify_by_pspec(
G_OBJECT(self), view_properties[PROP_FIXATE]);
} }
} }

View File

@ -60,7 +60,8 @@ typedef enum _FivViewCommand {
XX(FIV_VIEW_COMMAND_ZOOM_1, "zoom-1") \ XX(FIV_VIEW_COMMAND_ZOOM_1, "zoom-1") \
XX(FIV_VIEW_COMMAND_FIT_WIDTH, "fit-width") \ XX(FIV_VIEW_COMMAND_FIT_WIDTH, "fit-width") \
XX(FIV_VIEW_COMMAND_FIT_HEIGHT, "fit-height") \ XX(FIV_VIEW_COMMAND_FIT_HEIGHT, "fit-height") \
XX(FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT, "toggle-scale-to-fit") XX(FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT, "toggle-scale-to-fit") \
XX(FIV_VIEW_COMMAND_TOGGLE_FIXATE, "toggle-fixate")
#define XX(constant, name) constant, #define XX(constant, name) constant,
FIV_VIEW_COMMANDS(XX) FIV_VIEW_COMMANDS(XX)
#undef XX #undef XX

6
fiv.c
View File

@ -497,13 +497,13 @@ show_about_dialog(GtkWidget *parent)
XX(PLAY_PAUSE, B("media-playback-start-symbolic", "Pause")) \ XX(PLAY_PAUSE, B("media-playback-start-symbolic", "Pause")) \
XX(SEEK_FORWARD, B("media-seek-forward-symbolic", "Next frame")) \ XX(SEEK_FORWARD, B("media-seek-forward-symbolic", "Next frame")) \
XX(S3, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \ XX(S3, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \
XX(FIXATE, T("pin2-symbolic", "Keep zoom and position")) \
XX(MINUS, B("zoom-out-symbolic", "Zoom out")) \ XX(MINUS, B("zoom-out-symbolic", "Zoom out")) \
XX(SCALE, gtk_label_new("")) \ XX(SCALE, gtk_label_new("")) \
XX(PLUS, B("zoom-in-symbolic", "Zoom in")) \ XX(PLUS, B("zoom-in-symbolic", "Zoom in")) \
XX(ONE, B("zoom-original-symbolic", "Original size")) \ XX(ONE, B("zoom-original-symbolic", "Original size")) \
XX(FIT, T("zoom-fit-best-symbolic", "Scale to fit")) \ XX(FIT, T("zoom-fit-best-symbolic", "Scale to fit")) \
XX(S4, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \ XX(S4, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \
/* XX(PIN, B("view-pin-symbolic", "Keep view configuration")) */ \
/* Or perhaps "blur-symbolic", also in the extended set. */ \ /* Or perhaps "blur-symbolic", also in the extended set. */ \
XX(COLOR, T("preferences-color-symbolic", "Color management")) \ XX(COLOR, T("preferences-color-symbolic", "Color management")) \
XX(SMOOTH, T("blend-tool-symbolic", "Smooth scaling")) \ XX(SMOOTH, T("blend-tool-symbolic", "Smooth scaling")) \
@ -1523,6 +1523,7 @@ make_view_toolbar(void)
toolbar_command(TOOLBAR_PLUS, FIV_VIEW_COMMAND_ZOOM_IN); toolbar_command(TOOLBAR_PLUS, FIV_VIEW_COMMAND_ZOOM_IN);
toolbar_command(TOOLBAR_ONE, FIV_VIEW_COMMAND_ZOOM_1); toolbar_command(TOOLBAR_ONE, FIV_VIEW_COMMAND_ZOOM_1);
toolbar_toggler(TOOLBAR_FIT, "scale-to-fit"); toolbar_toggler(TOOLBAR_FIT, "scale-to-fit");
toolbar_toggler(TOOLBAR_FIXATE, "fixate");
toolbar_toggler(TOOLBAR_COLOR, "enable-cms"); toolbar_toggler(TOOLBAR_COLOR, "enable-cms");
toolbar_toggler(TOOLBAR_SMOOTH, "filter"); toolbar_toggler(TOOLBAR_SMOOTH, "filter");
toolbar_toggler(TOOLBAR_CHECKERBOARD, "checkerboard"); toolbar_toggler(TOOLBAR_CHECKERBOARD, "checkerboard");
@ -1541,6 +1542,8 @@ make_view_toolbar(void)
G_CALLBACK(on_notify_view_playing), NULL); G_CALLBACK(on_notify_view_playing), NULL);
g_signal_connect(g.view, "notify::scale-to-fit", g_signal_connect(g.view, "notify::scale-to-fit",
G_CALLBACK(on_notify_view_boolean), g.toolbar[TOOLBAR_FIT]); G_CALLBACK(on_notify_view_boolean), g.toolbar[TOOLBAR_FIT]);
g_signal_connect(g.view, "notify::fixate",
G_CALLBACK(on_notify_view_boolean), g.toolbar[TOOLBAR_FIXATE]);
g_signal_connect(g.view, "notify::enable-cms", g_signal_connect(g.view, "notify::enable-cms",
G_CALLBACK(on_notify_view_boolean), g.toolbar[TOOLBAR_COLOR]); G_CALLBACK(on_notify_view_boolean), g.toolbar[TOOLBAR_COLOR]);
g_signal_connect(g.view, "notify::filter", g_signal_connect(g.view, "notify::filter",
@ -1553,6 +1556,7 @@ make_view_toolbar(void)
g_object_notify(G_OBJECT(g.view), "scale"); g_object_notify(G_OBJECT(g.view), "scale");
g_object_notify(G_OBJECT(g.view), "playing"); g_object_notify(G_OBJECT(g.view), "playing");
g_object_notify(G_OBJECT(g.view), "scale-to-fit"); g_object_notify(G_OBJECT(g.view), "scale-to-fit");
g_object_notify(G_OBJECT(g.view), "fixate");
g_object_notify(G_OBJECT(g.view), "enable-cms"); g_object_notify(G_OBJECT(g.view), "enable-cms");
g_object_notify(G_OBJECT(g.view), "filter"); g_object_notify(G_OBJECT(g.view), "filter");
g_object_notify(G_OBJECT(g.view), "checkerboard"); g_object_notify(G_OBJECT(g.view), "checkerboard");

152
resources/pin2-symbolic.svg Normal file
View File

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<filter id="a" height="100%" width="100%" x="0%" y="0%">
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
</filter>
<mask id="b">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.3"/>
</g>
</mask>
<clipPath id="c">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="d">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="e">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="f">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="g">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="h">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="i">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="j">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="k">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="l">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="m">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="n">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="o">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="p">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.3"/>
</g>
</mask>
<clipPath id="q">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="r">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.5"/>
</g>
</mask>
<clipPath id="s">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="t">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.4"/>
</g>
</mask>
<clipPath id="u">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="v">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.4"/>
</g>
</mask>
<clipPath id="w">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="x">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.5"/>
</g>
</mask>
<clipPath id="y">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="z">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.5"/>
</g>
</mask>
<clipPath id="A">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<path d="m 14 28 c 0 3.3125 -2.6875 6 -6 6 s -6 -2.6875 -6 -6 s 2.6875 -6 6 -6 s 6 2.6875 6 6 z m 0 0" fill="none" stroke="#2e3436" stroke-linecap="round" stroke-width="2"/>
<path d="m 6.992188 2 l -5.070313 4.992188 l 3.261719 2.539062 c -0.519532 2.046875 0.078125 4.214844 1.566406 5.710938 l 3.742188 -3.742188 l 4.5 4.5 h 1 v -1 l -4.5 -4.5 l 3.746093 -3.742188 c -1.5 -1.496093 -3.679687 -2.089843 -5.730469 -1.5625 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
<path d="m 1.992188 7 l 5 -5" fill="none" stroke="#2e3436" stroke-linecap="square" stroke-width="2"/>
<g clip-path="url(#c)" mask="url(#b)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 562.460938 212.058594 h 10.449218 c -1.183594 0.492187 -1.296875 2.460937 0 3 h -10.449218 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#e)" mask="url(#d)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 16 632 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#g)" mask="url(#f)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 17 631 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#i)" mask="url(#h)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 18 634 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#k)" mask="url(#j)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 16 634 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#m)" mask="url(#l)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 17 635 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#o)" mask="url(#n)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 19 635 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#q)" mask="url(#p)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 136 660 v 7 h 7 v -7 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#s)" mask="url(#r)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 199 642 h 3 v 12 h -3 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#u)" mask="url(#t)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 209.5 144.160156 c 0.277344 0 0.5 0.222656 0.5 0.5 v 1 c 0 0.277344 -0.222656 0.5 -0.5 0.5 s -0.5 -0.222656 -0.5 -0.5 v -1 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#w)" mask="url(#v)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 206.5 144.160156 c 0.277344 0 0.5 0.222656 0.5 0.5 v 1 c 0 0.277344 -0.222656 0.5 -0.5 0.5 s -0.5 -0.222656 -0.5 -0.5 v -1 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#y)" mask="url(#x)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 229.5 143.160156 c -0.546875 0 -1 0.457032 -1 1 c 0 0.546875 0.453125 1 1 1 s 1 -0.453125 1 -1 c 0 -0.542968 -0.453125 -1 -1 -1 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#A)" mask="url(#z)" transform="matrix(1 0 0 1 -620 -624)">
<path d="m 226.453125 143.160156 c -0.519531 0 -0.953125 0.433594 -0.953125 0.953125 v 0.09375 c 0 0.519531 0.433594 0.953125 0.953125 0.953125 h 0.09375 c 0.519531 0 0.953125 -0.433594 0.953125 -0.953125 v -0.09375 c 0 -0.519531 -0.433594 -0.953125 -0.953125 -0.953125 z m 0 0" fill="#2e3436"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -10,5 +10,6 @@
<file preprocess="xml-stripblanks">checkerboard-symbolic.svg</file> <file preprocess="xml-stripblanks">checkerboard-symbolic.svg</file>
<file preprocess="xml-stripblanks">heal-symbolic.svg</file> <file preprocess="xml-stripblanks">heal-symbolic.svg</file>
<file preprocess="xml-stripblanks">info-symbolic.svg</file> <file preprocess="xml-stripblanks">info-symbolic.svg</file>
<file preprocess="xml-stripblanks">pin2-symbolic.svg</file>
</gresource> </gresource>
</gresources> </gresources>