Lagrange [release]
Added option to load image instead of scrolling
[1mdiff --git a/res/about/version.gmi b/res/about/version.gmi[m
[1mindex 3679a465..f3d40e76 100644[m
[1m--- a/res/about/version.gmi[m
[1m+++ b/res/about/version.gmi[m
[36m@@ -7,6 +7,7 @@[m
# Release notes[m
[m
## 0.10[m
[32m+[m[32m* Added option to load inline images when pressing Space or ↓. If an image link is visible, the image will be loaded instead of scrolling. This option is disabled by default.[m
* Added an option to use a proxy server for Gemini requests.[m
* Added a keybinding to activate keyboard link navigation mode (default is "F").[m
* Clearing and resetting keybindings via a context menu.[m
[1mdiff --git a/src/app.c b/src/app.c[m
[1mindex f2741ee6..b53666c8 100644[m
[1m--- a/src/app.c[m
[1m+++ b/src/app.c[m
[36m@@ -183,6 +183,7 @@[m [mstatic iString *serializePrefs_App_(const iApp *d) {[m
appendFormat_String(str, "prefs.mono.gopher.changed arg:%d\n", d->prefs.monospaceGopher);[m
appendFormat_String(str, "zoom.set arg:%d\n", d->prefs.zoomPercent);[m
appendFormat_String(str, "smoothscroll arg:%d\n", d->prefs.smoothScrolling);[m
[32m+[m[32m appendFormat_String(str, "imageloadscroll arg:%d\n", d->prefs.loadImageInsteadOfScrolling);[m
appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth);[m
appendFormat_String(str, "prefs.biglede.changed arg:%d\n", d->prefs.bigFirstParagraph);[m
appendFormat_String(str, "prefs.sideicon.changed arg:%d\n", d->prefs.sideIcon);[m
[36m@@ -746,6 +747,8 @@[m [mstatic iBool handlePrefsCommands_(iWidget *d, const char *cmd) {[m
isSelected_Widget(findChild_Widget(d, "prefs.retainwindow")));[m
postCommandf_App("smoothscroll arg:%d",[m
isSelected_Widget(findChild_Widget(d, "prefs.smoothscroll")));[m
[32m+[m[32m postCommandf_App("imageloadscroll arg:%d",[m
[32m+[m[32m isSelected_Widget(findChild_Widget(d, "prefs.imageloadscroll")));[m
postCommandf_App("ostheme arg:%d",[m
isSelected_Widget(findChild_Widget(d, "prefs.ostheme")));[m
postCommandf_App("proxy.gemini address:%s",[m
[36m@@ -956,6 +959,10 @@[m [miBool handleCommand_App(const char *cmd) {[m
d->prefs.smoothScrolling = arg_Command(cmd);[m
return iTrue;[m
}[m
[32m+[m[32m else if (equal_Command(cmd, "imageloadscroll")) {[m
[32m+[m[32m d->prefs.loadImageInsteadOfScrolling = arg_Command(cmd);[m
[32m+[m[32m return iTrue;[m
[32m+[m[32m }[m
else if (equal_Command(cmd, "forcewrap.toggle")) {[m
d->prefs.forceLineWrap = !d->prefs.forceLineWrap;[m
updateSize_DocumentWidget(document_App());[m
[36m@@ -1153,6 +1160,7 @@[m [miBool handleCommand_App(const char *cmd) {[m
setText_InputWidget(findChild_Widget(dlg, "prefs.downloads"), &d->prefs.downloadDir);[m
setToggle_Widget(findChild_Widget(dlg, "prefs.hoveroutline"), d->prefs.hoverOutline);[m
setToggle_Widget(findChild_Widget(dlg, "prefs.smoothscroll"), d->prefs.smoothScrolling);[m
[32m+[m[32m setToggle_Widget(findChild_Widget(dlg, "prefs.imageloadscroll"), d->prefs.loadImageInsteadOfScrolling);[m
setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme);[m
setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize);[m
setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"),[m
[1mdiff --git a/src/gmdocument.c b/src/gmdocument.c[m
[1mindex 923c20c9..62a0ba55 100644[m
[1m--- a/src/gmdocument.c[m
[1m+++ b/src/gmdocument.c[m
[36m@@ -1366,8 +1366,14 @@[m [menum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum[m
}[m
[m
iBool isMediaLink_GmDocument(const iGmDocument *d, iGmLinkId linkId) {[m
[31m- return (linkFlags_GmDocument(d, linkId) &[m
[31m- (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag)) != 0;[m
[32m+[m[32m const iString *dstUrl = absoluteUrl_String(&d->url, linkUrl_GmDocument(d, linkId));[m
[32m+[m[32m const iRangecc scheme = urlScheme_String(dstUrl);[m
[32m+[m[32m if (equalCase_Rangecc(scheme, "gemini") || equalCase_Rangecc(scheme, "gopher") ||[m
[32m+[m[32m willUseProxy_App(scheme)) {[m
[32m+[m[32m return (linkFlags_GmDocument(d, linkId) &[m
[32m+[m[32m (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag)) != 0;[m
[32m+[m[32m }[m
[32m+[m[32m return iFalse;[m
}[m
[m
const iString *title_GmDocument(const iGmDocument *d) {[m
[1mdiff --git a/src/prefs.c b/src/prefs.c[m
[1mindex 80b11c30..dc2bd601 100644[m
[1m--- a/src/prefs.c[m
[1m+++ b/src/prefs.c[m
[36m@@ -24,21 +24,23 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
[m
void init_Prefs(iPrefs *d) {[m
d->dialogTab = 0;[m
[31m- d->theme = dark_ColorTheme;[m
d->useSystemTheme = iTrue;[m
[32m+[m[32m d->theme = dark_ColorTheme;[m
d->retainWindowSize = iTrue;[m
[32m+[m[32m d->uiScale = 1.0f; /* default set elsewhere */[m
d->zoomPercent = 100;[m
[32m+[m[32m d->sideIcon = iTrue;[m
[32m+[m[32m d->hoverOutline = iFalse;[m
d->smoothScrolling = iTrue;[m
[31m- d->forceLineWrap = iFalse;[m
[31m- d->quoteIcon = iTrue;[m
[32m+[m[32m d->loadImageInsteadOfScrolling = iFalse;[m
d->font = nunito_TextFont;[m
d->headingFont = nunito_TextFont;[m
d->monospaceGemini = iFalse;[m
d->monospaceGopher = iFalse;[m
d->lineWidth = 40;[m
d->bigFirstParagraph = iTrue;[m
[31m- d->sideIcon = iTrue;[m
[31m- d->hoverOutline = iFalse;[m
[32m+[m[32m d->forceLineWrap = iFalse;[m
[32m+[m[32m d->quoteIcon = iTrue;[m
d->docThemeDark = colorfulDark_GmDocumentTheme;[m
d->docThemeLight = white_GmDocumentTheme;[m
d->saturation = 1.0f;[m
[1mdiff --git a/src/prefs.h b/src/prefs.h[m
[1mindex 33ce8b41..a3993629 100644[m
[1m--- a/src/prefs.h[m
[1m+++ b/src/prefs.h[m
[36m@@ -33,31 +33,37 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
iDeclareType(Prefs)[m
[m
struct Impl_Prefs {[m
[31m- int dialogTab;[m
[31m- iBool retainWindowSize;[m
[31m- float uiScale;[m
[31m- int zoomPercent;[m
[31m- iBool smoothScrolling;[m
[31m- iBool useSystemTheme;[m
[32m+[m[32m /* UI state */[m
[32m+[m[32m int dialogTab;[m
[32m+[m[32m /* Window */[m
[32m+[m[32m iBool useSystemTheme;[m
enum iColorTheme theme;[m
[31m- iString geminiProxy;[m
[31m- iString gopherProxy;[m
[31m- iString httpProxy;[m
[31m- iString downloadDir;[m
[31m- /* Content */[m
[31m- enum iTextFont font;[m
[31m- enum iTextFont headingFont;[m
[31m- iBool monospaceGemini;[m
[31m- iBool monospaceGopher;[m
[31m- int lineWidth;[m
[31m- iBool bigFirstParagraph;[m
[31m- iBool forceLineWrap;[m
[31m- iBool quoteIcon;[m
[31m- iBool sideIcon;[m
[31m- iBool hoverOutline;[m
[32m+[m[32m iBool retainWindowSize;[m
[32m+[m[32m float uiScale;[m
[32m+[m[32m int zoomPercent;[m
[32m+[m[32m iBool sideIcon;[m
[32m+[m[32m /* Behavior */[m
[32m+[m[32m iString downloadDir;[m
[32m+[m[32m iBool hoverOutline;[m
[32m+[m[32m iBool smoothScrolling;[m
[32m+[m[32m iBool loadImageInsteadOfScrolling;[m
[32m+[m[32m /* Network */[m
[32m+[m[32m iString geminiProxy;[m
[32m+[m[32m iString gopherProxy;[m
[32m+[m[32m iString httpProxy;[m
[32m+[m[32m /* Style */[m
[32m+[m[32m enum iTextFont font;[m
[32m+[m[32m enum iTextFont headingFont;[m
[32m+[m[32m iBool monospaceGemini;[m
[32m+[m[32m iBool monospaceGopher;[m
[32m+[m[32m int lineWidth;[m
[32m+[m[32m iBool bigFirstParagraph;[m
[32m+[m[32m iBool forceLineWrap;[m
[32m+[m[32m iBool quoteIcon;[m
[32m+[m[32m /* Colors */[m
enum iGmDocumentTheme docThemeDark;[m
enum iGmDocumentTheme docThemeLight;[m
[31m- float saturation;[m
[32m+[m[32m float saturation;[m
};[m
[m
iDeclareTypeConstruction(Prefs)[m
[1mdiff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c[m
[1mindex 59f5a92c..0fc969ba 100644[m
[1m--- a/src/ui/documentwidget.c[m
[1m+++ b/src/ui/documentwidget.c[m
[36m@@ -1151,10 +1151,8 @@[m [mstatic iMediaRequest *findMediaRequest_DocumentWidget_(const iDocumentWidget *d,[m
[m
static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId) {[m
if (!findMediaRequest_DocumentWidget_(d, linkId)) {[m
[31m- pushBack_ObjectList([m
[31m- d->media,[m
[31m- iClob(new_MediaRequest([m
[31m- d, linkId, absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->doc, linkId)))));[m
[32m+[m[32m const iString *imageUrl = absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->doc, linkId));[m
[32m+[m[32m pushBack_ObjectList(d->media, iClob(new_MediaRequest(d, linkId, imageUrl)));[m
invalidate_DocumentWidget_(d);[m
return iTrue;[m
}[m
[36m@@ -1235,6 +1233,22 @@[m [mstatic void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) {[m
}[m
}[m
[m
[32m+[m[32mstatic iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) {[m
[32m+[m[32m iConstForEach(PtrArray, i, &d->visibleLinks) {[m
[32m+[m[32m const iGmRun *run = i.ptr;[m
[32m+[m[32m if (run->linkId && !run->imageId && ~run->flags & decoration_GmRunFlag) {[m
[32m+[m[32m const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId);[m
[32m+[m[32m if (isMediaLink_GmDocument(d->doc, run->linkId) &&[m
[32m+[m[32m ~linkFlags & content_GmLinkFlag && ~linkFlags & permanent_GmLinkFlag ) {[m
[32m+[m[32m if (requestMedia_DocumentWidget_(d, run->linkId)) {[m
[32m+[m[32m return iTrue;[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m return iFalse;[m
[32m+[m[32m}[m
[32m+[m
static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {[m
iWidget *w = as_Widget(d);[m
if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) {[m
[36m@@ -1572,13 +1586,16 @@[m [mstatic iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)[m
return iTrue;[m
}[m
else if (equal_Command(cmd, "scroll.page") && document_App() == d) {[m
[31m- if (argLabel_Command(cmd, "repeat")) {[m
[31m- /* TODO: Adjust scroll animation to be linear during repeated scroll? */[m
[32m+[m[32m const int dir = arg_Command(cmd);[m
[32m+[m[32m if (!argLabel_Command(cmd, "repeat") && prefs_App()->loadImageInsteadOfScrolling &&[m
[32m+[m[32m dir > 0) {[m
[32m+[m[32m if (fetchNextUnfetchedImage_DocumentWidget_(d)) {[m
[32m+[m[32m return iTrue;[m
[32m+[m[32m }[m
}[m
smoothScroll_DocumentWidget_(d,[m
[31m- arg_Command(cmd) *[m
[31m- (0.5f * height_Rect(documentBounds_DocumentWidget_(d)) -[m
[31m- 0 * lineHeight_Text(paragraph_FontId)),[m
[32m+[m[32m dir * (0.5f * height_Rect(documentBounds_DocumentWidget_(d)) -[m
[32m+[m[32m 0 * lineHeight_Text(paragraph_FontId)),[m
smoothDuration_DocumentWidget_);[m
return iTrue;[m
}[m
[36m@@ -1599,8 +1616,15 @@[m [mstatic iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)[m
return iTrue;[m
}[m
else if (equal_Command(cmd, "scroll.step") && document_App() == d) {[m
[32m+[m[32m const int dir = arg_Command(cmd);[m
[32m+[m[32m if (!argLabel_Command(cmd, "repeat") && prefs_App()->loadImageInsteadOfScrolling &&[m
[32m+[m[32m dir > 0) {[m
[32m+[m[32m if (fetchNextUnfetchedImage_DocumentWidget_(d)) {[m
[32m+[m[32m return iTrue;[m
[32m+[m[32m }[m
[32m+[m[32m }[m
smoothScroll_DocumentWidget_(d,[m
[31m- 3 * lineHeight_Text(paragraph_FontId) * arg_Command(cmd),[m
[32m+[m[32m 3 * lineHeight_Text(paragraph_FontId) * dir,[m
smoothDuration_DocumentWidget_);[m
return iTrue;[m
}[m
[1mdiff --git a/src/ui/keys.c b/src/ui/keys.c[m
[1mindex 5b88bfcf..ea874343 100644[m
[1m--- a/src/ui/keys.c[m
[1m+++ b/src/ui/keys.c[m
[36m@@ -56,25 +56,46 @@[m [mstatic void clear_Keys_(iKeys *d) {[m
}[m
}[m
[m
[32m+[m[32menum iBindFlag {[m
[32m+[m[32m argRepeat_BindFlag = iBit(1),[m
[32m+[m[32m};[m
[32m+[m
/* TODO: This indirection could be used for localization, although all UI strings[m
would need to be similarly handled. */[m
[31m-static const struct { int id; iMenuItem bind; } defaultBindings_[] = {[m
[31m- { 1, { "Jump to top", SDLK_HOME, 0, "scroll.top" } },[m
[31m- { 2, { "Jump to bottom", SDLK_END, 0, "scroll.bottom" } },[m
[31m- { 10, { "Scroll up", SDLK_UP, 0, "scroll.step arg:-1" } },[m
[31m- { 11, { "Scroll down", SDLK_DOWN, 0, "scroll.step arg:1" } },[m
[31m- { 20, { "Scroll up half a page", SDLK_PAGEUP, 0, "scroll.page arg:-1" } },[m
[31m- { 21, { "Scroll down half a page", SDLK_PAGEDOWN, 0, "scroll.page arg:1" } },[m
[31m- { 30, { "Go back", navigateBack_KeyShortcut, "navigate.back" } },[m
[31m- { 31, { "Go forward", navigateForward_KeyShortcut, "navigate.forward" } },[m
[31m- { 32, { "Go to parent directory", navigateParent_KeyShortcut, "navigate.parent" } },[m
[31m- { 33, { "Go to site root", navigateRoot_KeyShortcut, "navigate.root" } },[m
[31m- { 40, { "Open link via keyboard", 'f', 0, "document.linkkeys"} },[m
[32m+[m[32mstatic const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] = {[m
[32m+[m[32m { 1, { "Jump to top", SDLK_HOME, 0, "scroll.top" }, 0 },[m
[32m+[m[32m { 2, { "Jump to bottom", SDLK_END, 0, "scroll.bottom" }, 0 },[m
[32m+[m[32m { 10, { "Scroll up", SDLK_UP, 0, "scroll.step arg:-1" }, argRepeat_BindFlag },[m
[32m+[m[32m { 11, { "Scroll down", SDLK_DOWN, 0, "scroll.step arg:1" }, argRepeat_BindFlag },[m
[32m+[m[32m { 20, { "Scroll up half a page", SDLK_PAGEUP, 0, "scroll.page arg:-1" }, argRepeat_BindFlag },[m
[32m+[m[32m { 21, { "Scroll down half a page", SDLK_PAGEDOWN, 0, "scroll.page arg:1" }, argRepeat_BindFlag },[m
[32m+[m[32m { 30, { "Go back", navigateBack_KeyShortcut, "navigate.back" }, 0 },[m
[32m+[m[32m { 31, { "Go forward", navigateForward_KeyShortcut, "navigate.forward" }, 0 },[m
[32m+[m[32m { 32, { "Go to parent directory", navigateParent_KeyShortcut, "navigate.parent" }, 0 },[m
[32m+[m[32m { 33, { "Go to site root", navigateRoot_KeyShortcut, "navigate.root" }, 0 },[m
[32m+[m[32m { 40, { "Open link via keyboard", 'f', 0, "document.linkkeys" }, 0 },[m
/* The following cannot currently be changed (built-in duplicates). */[m
[31m- { 1000, { NULL, SDLK_SPACE, KMOD_SHIFT, "scroll.page arg:-1" } },[m
[31m- { 1001, { NULL, SDLK_SPACE, 0, "scroll.page arg:1" } },[m
[32m+[m[32m { 1000, { NULL, SDLK_SPACE, KMOD_SHIFT, "scroll.page arg:-1" }, argRepeat_BindFlag },[m
[32m+[m[32m { 1001, { NULL, SDLK_SPACE, 0, "scroll.page arg:1" }, argRepeat_BindFlag },[m
};[m
[m
[32m+[m[32mstatic iBinding *findId_Keys_(iKeys *d, int id) {[m
[32m+[m[32m iForEach(Array, i, &d->bindings) {[m
[32m+[m[32m iBinding *bind = i.value;[m
[32m+[m[32m if (bind->id == id) {[m
[32m+[m[32m return bind;[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m return NULL;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic void setFlags_Keys_(int id, int bindFlags) {[m
[32m+[m[32m iBinding *bind = findId_Keys_(&keys_, id);[m
[32m+[m[32m if (bind) {[m
[32m+[m[32m bind->flags = bindFlags;[m
[32m+[m[32m }[m
[32m+[m[32m}[m
[32m+[m
static void bindDefaults_(void) {[m
iForIndices(i, defaultBindings_) {[m
const int id = defaultBindings_[i].id;[m
[36m@@ -83,6 +104,7 @@[m [mstatic void bindDefaults_(void) {[m
if (bind.label) {[m
setLabel_Keys(id, bind.label);[m
}[m
[32m+[m[32m setFlags_Keys_(id, defaultBindings_[i].flags);[m
}[m
}[m
[m
[36m@@ -95,16 +117,6 @@[m [mstatic iBinding *find_Keys_(iKeys *d, int key, int mods) {[m
return NULL;[m
}[m
[m
[31m-static iBinding *findId_Keys_(iKeys *d, int id) {[m
[31m- iForEach(Array, i, &d->bindings) {[m
[31m- iBinding *bind = i.value;[m
[31m- if (bind->id == id) {[m
[31m- return bind;[m
[31m- }[m
[31m- }[m
[31m- return NULL;[m
[31m-}[m
[31m-[m
static iBinding *findCommand_Keys_(iKeys *d, const char *command) {[m
/* Note: O(n) */[m
iForEach(Array, i, &d->bindings) {[m
[36m@@ -259,7 +271,12 @@[m [miBool processEvent_Keys(const SDL_Event *ev) {[m
if (ev->type == SDL_KEYDOWN) {[m
const iBinding *bind = find_Keys_(d, ev->key.keysym.sym, keyMods_Sym(ev->key.keysym.mod));[m
if (bind) {[m
[31m- postCommandString_App(&bind->command);[m
[32m+[m[32m if (ev->key.repeat && (bind->flags & argRepeat_BindFlag)) {[m
[32m+[m[32m postCommandf_App("%s repeat:1", cstr_String(&bind->command));[m
[32m+[m[32m }[m
[32m+[m[32m else {[m
[32m+[m[32m postCommandString_App(&bind->command);[m
[32m+[m[32m }[m
return iTrue;[m
}[m
}[m
[1mdiff --git a/src/ui/keys.h b/src/ui/keys.h[m
[1mindex 0cd97e2a..1e676c59 100644[m
[1m--- a/src/ui/keys.h[m
[1m+++ b/src/ui/keys.h[m
[36m@@ -52,6 +52,7 @@[m [miDeclareType(Binding)[m
[m
struct Impl_Binding {[m
int id;[m
[32m+[m[32m int flags;[m
int key;[m
int mods;[m
iString command;[m
[1mdiff --git a/src/ui/util.c b/src/ui/util.c[m
[1mindex 85d3562f..559c5381 100644[m
[1m--- a/src/ui/util.c[m
[1m+++ b/src/ui/util.c[m
[36m@@ -1013,6 +1013,8 @@[m [miWidget *makePreferences_Widget(void) {[m
addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoveroutline")));[m
addChild_Widget(headings, iClob(makeHeading_Widget("Smooth scrolling:")));[m
addChild_Widget(values, iClob(makeToggle_Widget("prefs.smoothscroll")));[m
[32m+[m[32m addChild_Widget(headings, iClob(makeHeading_Widget("Load image on scroll:")));[m
[32m+[m[32m addChild_Widget(values, iClob(makeToggle_Widget("prefs.imageloadscroll")));[m
}[m
/* Window. */ {[m
appendTwoColumnPage_(tabs, "Window", '2', &headings, &values);[m