From b35539d9181bbef06da4fe7f61e55cad822df756 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaakko=20Kera=CC=88nen?= jaakko.keranen@iki.fi
Date: Sat, 27 Feb 2021 12:28:24 +0200
Subject: [PATCH 1/1] Merged changes for v1.2 from dev branch
.gitignore | 3 +-
CMakeLists.txt | 62 +-
Depends-iOS.cmake | 21 +
Depends.cmake | 20 +
res/LaunchScreen.storyboard | 7 +
res/MacOSXBundleInfo.plist.in | 15 +
res/about/help.gmi | 43 +-
res/about/version.gmi | 49 +-
res/fi.skyjake.Lagrange.appdata.xml | 20 +
src/app.c | 340 +++-
src/app.h | 11 +
src/audio/player.c | 11 +-
src/bookmarks.c | 39 +-
src/bookmarks.h | 1 +
src/feeds.c | 5 +-
src/gmdocument.c | 117 +-
src/gmdocument.h | 11 +-
src/gmrequest.c | 21 +-
src/gmrequest.h | 1 +
src/gmutil.c | 51 +-
src/gmutil.h | 3 +
src/gopher.c | 14 +-
src/history.c | 12 +
src/history.h | 1 +
src/ios.h | 33 +
src/ios.m | 105 ++
src/main.c | 15 +-
src/media.c | 183 +-
src/media.h | 29 +-
src/mimehooks.c | 125 +-
src/prefs.c | 8 +
src/prefs.h | 4 +
src/stb_image_resize.h | 2631 +++++++++++++++++++++++++++
src/ui/bindingswidget.c | 4 +-
src/ui/color.c | 213 ++-
src/ui/color.h | 10 +
src/ui/documentwidget.c | 720 ++++----
src/ui/documentwidget.h | 1 +
src/ui/indicatorwidget.c | 1 +
src/ui/inputwidget.c | 123 +-
src/ui/inputwidget.h | 1 +
src/ui/keys.c | 4 +
src/ui/labelwidget.c | 47 +-
src/ui/labelwidget.h | 1 +
src/ui/listwidget.c | 20 +-
src/ui/lookupwidget.c | 22 +-
src/ui/{playerui.c => mediaui.c} | 111 +-
src/ui/{playerui.h => mediaui.h} | 17 +
src/ui/scrollwidget.c | 4 +-
src/ui/sidebarwidget.c | 199 +-
src/ui/sidebarwidget.h | 11 +-
src/ui/text.c | 99 +-
src/ui/text.h | 12 +-
src/ui/touch.c | 413 +++++
src/ui/touch.h | 32 +
src/ui/util.c | 372 ++--
src/ui/util.h | 5 +-
src/ui/widget.c | 167 +-
src/ui/widget.h | 33 +-
src/ui/window.c | 962 +++++++++-
src/ui/window.h | 39 +-
src/win32.c | 133 ++
src/win32.h | 11 +
63 files changed, 6753 insertions(+), 1045 deletions(-)
create mode 100644 Depends-iOS.cmake
create mode 100644 Depends.cmake
create mode 100644 res/LaunchScreen.storyboard
create mode 100644 src/ios.h
create mode 100644 src/ios.m
create mode 100644 src/stb_image_resize.h
rename src/ui/{playerui.c => mediaui.c} (67%)
rename src/ui/{playerui.h => mediaui.h} (76%)
create mode 100644 src/ui/touch.c
create mode 100644 src/ui/touch.h
diff --git a/.gitignore b/.gitignore
index 05eba91e..9ef6983d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,5 +4,4 @@
build-*
/.vsbuild
/.vscode
+/app
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9489f5a9..e13fc2d5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,7 +18,7 @@
cmake_minimum_required (VERSION 3.9)
project (Lagrange
DESCRIPTION "A Beautiful Gemini Client"
LANGUAGES C
)
@@ -33,24 +33,11 @@ option (ENABLE_RELATIVE_EMBED "Resources should always be found via relative p
option (ENABLE_WINDOWPOS_FIX "Set position after showing window (workaround for SDL bug)" OFF)
option (ENABLE_IDLE_SLEEP "While idle, sleep in the main thread instead of waiting for events" ON)
option (ENABLE_DOWNLOAD_EDIT "Allow changing the Downloads directory" ON)
+option (ENABLE_CUSTOM_FRAME "Draw a custom window frame (Windows)" OFF)
include (BuildType.cmake)
include (res/Embed.cmake)
-if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/CMakeLists.txt)
-else ()
-endif ()
-find_package (PkgConfig REQUIRED)
-pkg_check_modules (SDL2 REQUIRED sdl2)
-pkg_check_modules (MPG123 IMPORTED_TARGET libmpg123)
+include (Depends.cmake)
message (STATUS "Preparing embedded resources...")
@@ -83,7 +70,7 @@ set (EMBED_RESOURCES
res/fonts/SourceSansPro-Bold.ttf
res/fonts/Symbola.ttf
)
-if (UNIX AND NOT APPLE)
+if ((UNIX AND NOT APPLE) OR MSYS)
list (APPEND EMBED_RESOURCES res/lagrange-64.png)
endif ()
embed_make (${EMBED_RESOURCES})
@@ -122,6 +109,7 @@ set (SOURCES
src/prefs.c
src/prefs.h
src/stb_image.h
src/stb_truetype.h
src/visited.c
src/visited.h
@@ -154,14 +142,16 @@ set (SOURCES
src/ui/metrics.h
src/ui/paint.c
src/ui/paint.h
src/ui/scrollwidget.c
src/ui/scrollwidget.h
src/ui/sidebarwidget.c
src/ui/sidebarwidget.h
src/ui/text.c
src/ui/text.h
src/ui/util.c
src/ui/util.h
src/ui/visbuf.c
@@ -184,7 +174,18 @@ set (SOURCES
${CMAKE_CURRENT_BINARY_DIR}/embedded.h
)
if (IOS)
src/ios.m
src/ios.h
app/Images.xcassets
res/LaunchScreen.storyboard
MACOSX_PACKAGE_LOCATION Resources
elseif (APPLE)
list (APPEND SOURCES src/macos.m src/macos.h)
list (APPEND RESOURCES "res/Lagrange.icns")
endif ()
@@ -231,6 +232,9 @@ endif ()
if (ENABLE_DOWNLOAD_EDIT)
target_compile_definitions (app PUBLIC LAGRANGE_DOWNLOAD_EDIT=1)
endif ()
+if (ENABLE_CUSTOM_FRAME AND MSYS)
+endif ()
target_link_libraries (app PUBLIC the_Foundation::the_Foundation)
target_link_libraries (app PUBLIC ${SDL2_LDFLAGS})
if (APPLE)
@@ -239,13 +243,15 @@ if (APPLE)
else ()
target_link_libraries (app PUBLIC "-framework AppKit")
endif ()
target_compile_options (app PUBLIC -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
target_link_options (app PUBLIC -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
endif ()
set_property (TARGET app PROPERTY BUILD_RPATH ${SDL2_LIBRARY_DIRS})
set_target_properties (app PROPERTIES
OUTPUT_NAME "Lagrange"
BUILD_RPATH ${SDL2_LIBRARY_DIRS}
MACOSX_BUNDLE YES
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_LIST_DIR}/res/MacOSXBundleInfo.plist.in"
MACOSX_BUNDLE_BUNDLE_NAME "Lagrange"
@@ -258,9 +264,21 @@ if (APPLE)
MACOSX_BUNDLE_COPYRIGHT "© ${COPYRIGHT_YEAR} Jaakko Keränen"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "fi.skyjake.Lagrange"
)
set_target_properties (app PROPERTIES
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
MACOSX_BUNDLE_ICON_FILE "AppIcon"
)
set_property (TARGET app PROPERTY
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${XCODE_DEVELOPMENT_TEAM}
)
endif ()
if (MSYS)
endif ()
if (UNIX)
target_link_libraries (app PUBLIC m)
diff --git a/Depends-iOS.cmake b/Depends-iOS.cmake
new file mode 100644
index 00000000..013ee09a
--- /dev/null
+++ b/Depends-iOS.cmake
@@ -0,0 +1,21 @@
+message (STATUS "iOS dependency directory: ${IOS_DIR}")
+find_package (the_Foundation REQUIRED)
+set (SDL2_INCLUDE_DIRS ${IOS_DIR}/include/SDL2)
+set (SDL2_LDFLAGS
+)
diff --git a/Depends.cmake b/Depends.cmake
new file mode 100644
index 00000000..b4dacf7c
--- /dev/null
+++ b/Depends.cmake
@@ -0,0 +1,20 @@
+if (IOS)
+endif ()
+if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/CMakeLists.txt)
+else ()
+endif ()
+find_package (PkgConfig REQUIRED)
+pkg_check_modules (SDL2 REQUIRED sdl2)
+pkg_check_modules (MPG123 IMPORTED_TARGET libmpg123)
diff --git a/res/LaunchScreen.storyboard b/res/LaunchScreen.storyboard
new file mode 100644
index 00000000..f9a048ed
--- /dev/null
+++ b/res/LaunchScreen.storyboard
@@ -0,0 +1,7 @@
+
+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
+
diff --git a/res/MacOSXBundleInfo.plist.in b/res/MacOSXBundleInfo.plist.in
index 1d769768..186e733b 100644
--- a/res/MacOSXBundleInfo.plist.in
+++ b/res/MacOSXBundleInfo.plist.in
@@ -34,6 +34,21 @@
<true/>
<key>NSRequiresAquaSystemAppearance</key>
<false/>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
diff --git a/res/about/help.gmi b/res/about/help.gmi
index fcdc8239..15485571 100644
--- a/res/about/help.gmi
+++ b/res/about/help.gmi
@@ -6,6 +6,13 @@
+## What is Gemini
+Gemini is a simple protocol for serving content over the internet. It specifies a Markdown inspired format allowing basic plain text document markup. Compared to HTTP and HTML, Gemini is vastly simpler and easier to work with.
+=> gemini://gemini.circumlunar.space/docs/faq.gmi Project Gemini FAQ
+=> gemini://gemini.circumlunar.space/docs/specification.gmi Protocol and 'text/gemini' specification
Lagrange is a GUI client for browsing Geminispace. It offers modern conveniences familiar from web browsers, such as smooth scrolling, inline image viewing, multiple tabs, visual themes, Unicode fonts, bookmarks, history, and page outlines.
@@ -24,11 +31,12 @@ Like Gemini, Lagrange has been designed with minimalism in mind. It depends on a
-* Subscribe to Gemini feeds
+* Subscribe to Gemini and Atom feeds
+* Search engine integration
@@ -41,13 +49,6 @@ Like Gemini, Lagrange has been designed with minimalism in mind. It depends on a
-## What is Gemini
-Gemini is a simple protocol for serving content over the internet. It specifies a Markdown inspired format allowing basic plain text document markup. Compared to HTTP and HTML, Gemini is vastly simpler and easier to work with.
-=> gemini://gemini.circumlunar.space/docs/faq.gmi Project Gemini FAQ
-=> gemini://gemini.circumlunar.space/docs/specification.gmi Protocol and 'text/gemini' specification
Modern web browsers are complex beasts. In fact, they are so complex that one can create a fully functional virtual machine inside one and run another operating system!
@@ -88,6 +89,8 @@ Search within cached pages is limited to the (small) set of pages that Lagrange
Note that the navigation stack is saved to a file when Lagrange is shut down and restored on the next launch. This means the next time you launch Lagrange, you can still search the contents of past pages. However, navigation stacks are tab-specific, so closing a tab will delete its history as well.
+You can also make online search queries via the URL input field. When a search URL is configured on the "Network" tab of Preferences, text entered in the URL field is passed to the search URL as a query parameter. A search query will only occur when Enter is pressed while the [⇒ Search Query] indicator is visible.
The type and destination of a link are indicated by the link's icon and color: ➤ links to the same domain, and 🌐 to a different domain. The colors are:
@@ -127,7 +130,13 @@ You can give it a try now with the link below. Either hold down ${ALT}, or press
Press ${CTRL+}T to open a new tab, and ${CTRL+}W to close the current tab. Right-clicking on buttons in the tab bar shows a context menu for additional tab-related functions.
-The set of open tabs is restored when you launch Lagrange.
+The set of open tabs and their full contents are saved when you quit the application and restored when relaunch it.
+### 1.2.1 Auto-reloading
+A tab can be set to auto-reload at given intervals. The setting is remembered until the tab is closed. This is helpful if you keep a periodically updated page open for longer periods of time.
+The feature can be found in the page context menu: right-click and select "Set Auto-Reload...".
@@ -149,6 +158,8 @@ Bookmarks are listed in alphabetic order in the sidebar. There is no support for
In addition to a title, bookmarks can have tags. Some tags have a special meaning, but you are free to enter whatever you want in the tags field. In quick search results, tags are given extra weight so they appear higher in results.
+By default bookmarks are assigned random icons. You can enter a custom icon for a bookmark in the bookmark edit dialog to make it easier to recognize a particular site at a glance. The icon must be a single Unicode character. It will appear in the bookmark list, tab titles, feed entry list, quick search results, and page top banners.
=> about:bookmarks The special page "about:bookmarks" is used for exporting bookmarks out of Lagrange.
@@ -179,13 +190,15 @@ Note that remote bookmarks are read-only: they cannot be edited or tagged. This
+* The "usericon" tag prevents a random icon to be selected for the bookmark. This tag is automatically applied when an icon character is entered in the bookmark editor.
-You may be familiar with RSS and Atom XML feeds from the web. Lagrange does not support RSS or Atom, only Gemini feeds. A Gemini feed is simply a regular 'text/gemini' page that contains one or more links whose labels are formatted in a particular way.
+You may be familiar with XML-based RSS and Atom feeds from the web. The Gemini equivalent of these is Gemini feeds. A Gemini feed is simply a regular 'text/gemini' page that contains one or more links whose labels are formatted in a particular way. This makes it very easy to write pages that clients can subscribe to.
=> gemini://gemini.circumlunar.space/docs/companion/subscription.gmi See "Subscribing to Gemini pages" for more information.
+Lagrange supports Gemini and Atom feed subscriptions. Atom feeds are automatically translated to the Gemini feed format so they can be viewed and subscribed to like a normal 'text/gemini' page. RSS feeds are not supported.
Subscriptions are managed via bookmarks. When you subscribe to a feed page, a bookmark is created and the special "subscribed" tag is applied on it. In the Bookmarks list, this is indicated by a ★ icon. There is no other difference between normal bookmarks and feed subscriptions — you may tag any bookmark as "subscribed" and Lagrange will look through it for feed-style links. The bookmark title is used as the feed title. This defaults to the top heading of the feed index page, but you can edit it to suit your needs.
Feeds are refreshed periodically while Lagrange is running, and also immediately after launching if it has been a while since the previous refresh. You may also manually refresh all feeds via the menus or by pressing ${SHIFT+}${CTRL+}R.
@@ -259,6 +272,8 @@ There are four different color schemes for the UI: two for dark mode, and two fo
On macOS, Lagrange will automatically switch between dark and light modes if the "Use system theme" setting is enabled. On other platforms you'll need to switch manually.
+On Windows, an option is provided to use a custom window frame. This provides a more consistent visual style for the application. However, the custom frame overrides default behaviors of the window, so if you are a Windows power user expect it to not support all the special interactions. For example, when pressing the Windows+Left/Right key, the window is resized to take over one half of the screen. Normally, Windows would then prompt to select another window to fill the rest of the screen, but this does not happen with the custom window frame.
Options for wide window sizes:
@@ -281,12 +296,14 @@ The "Style" tab of the Preferences dialog lets you customize the appearance and
The fonts for headings and other text are selected separately. This way one can achieve a greater number of style variations. Also, headings that are in a different font are more visually distinct and thus easier for one's eyes to scan.
-There are two sans-serif and two serif fonts:
+There are six fonts available:
+* "Source Sans Pro" is the Lagrange UI font.
+* "Iosevka" is the monospace font.
Other style options:
@@ -387,7 +404,7 @@ Any output that does not follow this format is considered to mean that the hook
Like other Lagrange configuration lines, mimehooks.txt has a simple line-oriented syntax. Lagrange must be restarted for changes to the configuration file to take effect.
+≈
Each hook is specified as three lines:
diff --git a/res/about/version.gmi b/res/about/version.gmi
index 7a08503b..8bd30dfb 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -6,6 +6,49 @@
+## 1.2
+New features:
+* Atom feed subscriptions: Atom XML documents are automatically converted to Gemini feed index pages. This is a built-in version of the Atom-to-Gemini example on the Help page.
+* Inline downloads: right-click on any link that is openable inside Lagrange and select "Download Linked File".
+* Editable bookmark icons: use your favorite Unicode character as the symbol to represent a bookmarked site.
+* Searching via URL field: non-URL text entered in the field is passed onto the configured search query URL (Preferences > Network). An indicator is shown if a query will take place.
+* Tab auto-reloading: configure a reloading interval using the page context menu ("Set Auto-Reload..."). Auto-reloading is part of the persistent state of the tab.
+* "Iosevka" and "Source Sans Pro" (the UI font) can be used as heading and body fonts.
+* User preference for aligning all pages to the top of the window.
+* Keybinding (F11) for toggling fullscreen mode. On macOS, the shortcut is ⌃⌘F as before.
+* Keybinding for finding text on page.
+UI design:
+* Enhanced navbar: adjusted spacing, URL field has a maximum width, tab titles have less pronounced borders.
+* Improved sidebar appearance: bold subheadings, larger feed icons, adjusted spacing, background color.
+* Font consistency: all UI elements use the same font (i.e., no more monospace input fields).
+* Added setting for UI accent color (teal, orange).
+* General fine-tuning of the color palette.
+* Dialog buttons are aligned to the right edge, leaving room for additional action buttons on the left.
+* Page Information button is embedded in the URL field.
+* Page Information dialog is attached to its button.
+* Site icons use a different color in tab titles for visual distinction.
+* Fade background behind modal dialogs.
+* Responsive page margins.
+* Windows: Added a custom window frame to replace the default Windows one. This looks nicer but does not behave exactly like a native window frame. Added a setting to Preferences for switching back to the default frame.
+Other changes:
+* Help is opened on first run instead of the "About Lagrange" page to make it easier to discover important Gemini links like the FAQ.
+* "Go to Root" respects a user name found in the URL. One can still "Go to Parent" to get above the user level.
+* Feed entries are sorted by refresh time if they are published on the same date.
+* Don't show future-dated feed entries in Feeds.
+* Middle-clicking on links: open new tab in background or foreground depending on the Shift key.
+* Shift+Insert can be used for pasting clipboard contents into input fields.
+* Removed a strange violet-on-green color theme pairing.
+Bug fixes:
+* Fixed text prompt dialogs closing and accepting the entered text when switching focus away from the app.
+* Scroll position remains fixed while horizontally resizing the window or sidebars.
+* Fixed a crash when opening the audio player menu.
+* Fixed Gopher requests that were using URL (percent) encoded characters.
+* Windows: Fixed a flash of white when the window is first opened.
@@ -251,7 +294,6 @@
-=> https://mpg123.org/ mpg123: MPEG audio player and decoder library
@@ -261,6 +303,7 @@
+=> https://mpg123.org/ mpg123: MPEG audio player and decoder library
@@ -272,8 +315,7 @@
-* Added audio playback with support for streaming. Supported audio formats in this release are WAV (PCM, mono/stereo, 8/16/24/32 integer/float) and Ogg Vorbis. Shoutout to Sean Barrett et al. for stb_vorbis:
-=> https://github.com/nothings/stb stb: single-file public domain libraries for C/C++
+* Added audio playback with support for streaming. Supported audio formats in this release are WAV (PCM, mono/stereo, 8/16/24/32 integer/float) and Ogg Vorbis. Shoutout to Sean Barrett et al. for stb_vorbis.
@@ -281,6 +323,7 @@
+=> https://github.com/nothings/stb stb: single-file public domain libraries for C/C++
diff --git a/res/fi.skyjake.Lagrange.appdata.xml b/res/fi.skyjake.Lagrange.appdata.xml
index 689a609a..355bf18d 100644
--- a/res/fi.skyjake.Lagrange.appdata.xml
+++ b/res/fi.skyjake.Lagrange.appdata.xml
@@ -45,6 +45,26 @@
<update_contact>jaakko.keranen@iki.fi</update_contact>
<releases>
<release version="1.2" date="2021-02-27">
<description>
<p> This is a major feature update that also has a number of user
interface design changes.
</p>
<p> New features include viewing and subscribing to Atom feeds,
downloading any link as a file, editable bookmark icons,
search engine integration, tab auto-reloading, fullscreen mode,
and new font options for page content.
</p>
<p> UI enhancements include improved navbar and sidebar appearance,
setting for UI accent color, and placement of dialog
buttons.
</p>
<p> The full release notes can be viewed inside the app by opening
the "about:version" page.
</p>
</description>
<url>https://github.com/skyjake/lagrange/releases/tag/v1.2.0</url>
</release>
<release version="1.1.4" date="2021-02-22">
<description>
<p>Bug fixes:</p>
diff --git a/src/app.c b/src/app.c
index f5833c95..17b51dd4 100644
--- a/src/app.c
+++ b/src/app.c
@@ -61,9 +61,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include <stdarg.h>
#include <errno.h>
-#if defined (iPlatformApple) && !defined (iPlatformIOS)
+#if defined (iPlatformAppleDesktop)
#endif
+#if defined (iPlatformAppleMobile)
+# include "ios.h"
+#endif
#if defined (iPlatformMsys)
#endif
@@ -73,10 +76,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
iDeclareType(App)
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
#define EMB_BIN "../../Resources/resources.lgr"
static const char *defaultDataDir_App_ = "~/Library/Application Support/fi.skyjake.Lagrange";
#endif
+#if defined (iPlatformAppleMobile)
+#define EMB_BIN "../../Resources/resources.lgr"
+static const char *defaultDataDir_App_ = "~/Library/Application Support";
+#endif
#if defined (iPlatformMsys)
#define EMB_BIN "../resources.lgr"
static const char *defaultDataDir_App_ = "~/AppData/Roaming/fi.skyjake.Lagrange";
@@ -119,6 +126,7 @@ struct Impl_App {
iStringList *launchCommands;
iBool isFinishedLaunching;
iTime lastDropTime; /* for detecting drops of multiple items */
/* Preferences: */
iBool commandEcho; /* --echo */
iBool forceSoftwareRender; /* --sw */
@@ -154,33 +162,45 @@ static iString *serializePrefs_App_(const iApp *d) {
iString *str = new_String();
const iSidebarWidget *sidebar = findWidget_App("sidebar");
const iSidebarWidget *sidebar2 = findWidget_App("sidebar2");
+#if defined (LAGRANGE_CUSTOM_FRAME)
+#endif
appendFormat_String(str, "window.retain arg:%d\n", d->prefs.retainWindowSize);
if (d->prefs.retainWindowSize) {
const iBool isMaximized = (SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_MAXIMIZED) != 0;
int w, h, x, y;
x = d->window->lastRect.pos.x;
y = d->window->lastRect.pos.y;
w = d->window->lastRect.size.x;
h = d->window->lastRect.size.y;
x = d->window->place.normalRect.pos.x;
y = d->window->place.normalRect.pos.y;
w = d->window->place.normalRect.size.x;
h = d->window->place.normalRect.size.y;
appendFormat_String(str, "window.setrect width:%d height:%d coord:%d %d\n", w, h, x, y);
appendFormat_String(str, "sidebar.width arg:%d\n", width_SidebarWidget(sidebar));
appendFormat_String(str, "sidebar2.width arg:%d\n", width_SidebarWidget(sidebar2));
/* On macOS, maximization should be applied at creation time or the window will take
a moment to animate to its maximized size. */
-#if !defined (iPlatformApple)
if (isMaximized) {
+#if defined (LAGRANGE_CUSTOM_FRAME)
if (snap_Window(d->window)) {
if (~SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_MINIMIZED) {
/* Save the actual visible window position, too, because snapped windows may
still be resized/moved without affecting normalRect. */
SDL_GetWindowPosition(d->window->win, &x, &y);
SDL_GetWindowSize(d->window->win, &w, &h);
appendFormat_String(
str, "~window.setrect snap:%d width:%d height:%d coord:%d %d\n",
snap_Window(d->window), w, h, x, y);
}
}
+#elif !defined (iPlatformApple)
if (snap_Window(d->window) == maximized_WindowSnap) {
appendFormat_String(str, "~window.maximize\n");
}
-#else
iUnused(isMaximized);
#endif
}
/* Sidebars. */ {
if (isVisible_Widget(sidebar)) {
if (isVisible_Widget(sidebar) && deviceType_App() != phone_AppDeviceType) {
appendCStr_String(str, "sidebar.toggle\n");
}
appendFormat_String(str, "sidebar.mode arg:%d\n", mode_SidebarWidget(sidebar));
if (isVisible_Widget(sidebar2)) {
if (isVisible_Widget(sidebar2) && deviceType_App() != phone_AppDeviceType) {
appendCStr_String(str, "sidebar2.toggle\n");
}
appendFormat_String(str, "sidebar2.mode arg:%d\n", mode_SidebarWidget(sidebar2));
@@ -199,9 +219,11 @@ static iString *serializePrefs_App_(const iApp *d) {
appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth);
appendFormat_String(str, "prefs.biglede.changed arg:%d\n", d->prefs.bigFirstParagraph);
appendFormat_String(str, "prefs.sideicon.changed arg:%d\n", d->prefs.sideIcon);
appendFormat_String(str, "quoteicon.set arg:%d\n", d->prefs.quoteIcon ? 1 : 0);
appendFormat_String(str, "prefs.hoverlink.changed arg:%d\n", d->prefs.hoverLink);
appendFormat_String(str, "theme.set arg:%d auto:1\n", d->prefs.theme);
appendFormat_String(str, "ostheme arg:%d\n", d->prefs.useSystemTheme);
appendFormat_String(str, "doctheme.dark.set arg:%d\n", d->prefs.docThemeDark);
appendFormat_String(str, "doctheme.light.set arg:%d\n", d->prefs.docThemeLight);
@@ -210,6 +232,7 @@ static iString *serializePrefs_App_(const iApp *d) {
appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->prefs.gopherProxy));
appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->prefs.httpProxy));
appendFormat_String(str, "downloads path:%s\n", cstr_String(&d->prefs.downloadDir));
return str;
}
@@ -270,7 +293,10 @@ static void loadPrefs_App_(iApp *d) {
if (equal_Command(cmd, "uiscale")) {
setUiScale_Window(get_Window(), argf_Command(cmd));
}
else if (equal_Command(cmd, "window.setrect")) {
else if (equal_Command(cmd, "customframe")) {
d->prefs.customFrame = arg_Command(cmd);
}
else if (equal_Command(cmd, "window.setrect") && !argLabel_Command(cmd, "snap")) {
const iInt2 pos = coord_Command(cmd);
d->initialWindowRect = init_Rect(
pos.x, pos.y, argLabel_Command(cmd, "width"), argLabel_Command(cmd, "height"));
@@ -285,6 +311,9 @@ static void loadPrefs_App_(iApp *d) {
else {
/* default preference values */
}
+#if !defined (LAGRANGE_CUSTOM_FRAME)
+#endif
iRelease(f);
}
@@ -370,6 +399,9 @@ static void saveState_App_(const iApp *d) {
serializeState_DocumentWidget(i.object, stream_File(f));
}
}
fprintf(stderr, "[App] failed to save state: %s\n", strerror(errno));
iRelease(f);
}
@@ -384,6 +416,12 @@ static uint32_t checkAsleep_App_(uint32_t interval, void *param) {
}
#endif
+static uint32_t postAutoReloadCommand_App_(uint32_t interval, void *param) {
+}
static void init_App_(iApp *d, int argc, char **argv) {
init_CommandLine(&d->args, argc, argv);
/* Where was the app started from? We ask SDL first because the command line alone is
@@ -438,8 +476,11 @@ static void init_App_(iApp *d, int argc, char **argv) {
d->lastEventTime = 0;
d->sleepTimer = SDL_AddTimer(1000, checkAsleep_App_, d);
#endif
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
setupApplication_MacOS();
+#if defined (iPlatformAppleMobile)
+#endif
#endif
init_Keys();
loadPrefs_App_(d);
@@ -476,9 +517,11 @@ static void init_App_(iApp *d, int argc, char **argv) {
/* Widget state init. */
processEvents_App(postedEventsOnly_AppEventMode);
if (!loadState_App_(d)) {
postCommand_App("navigate.home");
postCommand_App("open url:about:help");
}
postCommand_App("window.unfreeze");
d->isFinishedLaunching = iTrue;
/* Run any commands that were pending completion of launch. */ {
iForEach(StringList, i, d->launchCommands) {
@@ -539,6 +582,61 @@ const iString *downloadDir_App(void) {
return collect_String(cleaned_Path(&app_.prefs.downloadDir));
}
+const iString *downloadPathForUrl_App(const iString *url, const iString *mime) {
parts.path.start++;
parts.path.end--;
if (!isEmpty_Range(&parts.host)) {
setRange_String(name, parts.host);
replace_Block(&name->chars, '.', '_');
}
iRangecc fn = { parts.path.start + lastIndexOfCStr_Rangecc(parts.path, "/") + 1,
parts.path.end };
if (!isEmpty_Range(&fn)) {
setRange_String(name, fn);
}
/* This would be interpreted as a reference to a home directory. */
remove_Block(&name->chars, 0, 1);
/* No extension specified in URL. */
if (startsWith_String(mime, "text/gemini")) {
appendCStr_String(savePath, ".gmi");
}
else if (startsWith_String(mime, "text/")) {
appendCStr_String(savePath, ".txt");
}
else if (startsWith_String(mime, "image/")) {
appendCStr_String(savePath, cstr_String(mime) + 6);
}
/* Make it unique. */
iDate now;
initCurrent_Date(&now);
size_t insPos = lastIndexOfCStr_String(savePath, ".");
if (insPos == iInvalidPos) {
insPos = size_String(savePath);
}
const iString *date = collect_String(format_Date(&now, "_%Y-%m-%d_%H%M%S"));
insertData_Block(&savePath->chars, insPos, cstr_String(date), size_String(date));
+}
const iString *debugInfo_App(void) {
extern char **environ; /* The environment variables. */
iApp *d = &app_;
@@ -570,6 +668,39 @@ const iString *debugInfo_App(void) {
return msg;
}
+static void clearCache_App_(void) {
clearCache_History(history_DocumentWidget(i.object));
+}
+void trimCache_App(void) {
cacheSize += cacheSize_History(history_DocumentWidget(i.object));
iDocumentWidget *doc = i.object;
const size_t pruned = pruneLeastImportant_History(history_DocumentWidget(doc));
if (pruned) {
cacheSize -= pruned;
wasPruned = iTrue;
}
next_ObjectListIterator(&i);
if (!i.value) {
if (!wasPruned) break;
wasPruned = iFalse;
init_ObjectListIterator(&i, docs);
}
+}
iLocalDef iBool isWaitingAllowed_App_(iApp *d) {
#if defined (LAGRANGE_IDLE_SLEEP)
if (d->isIdling) {
@@ -587,10 +718,31 @@ void processEvents_App(enum iAppEventMode eventMode) {
SDL_WaitEvent(&ev)) ||
((!isWaitingAllowed_App_(d) || eventMode == postedEventsOnly_AppEventMode) &&
SDL_PollEvent(&ev))) {
+#if defined (iPlatformAppleMobile)
if (processEvent_iOS(&ev)) {
continue;
}
+#endif
switch (ev.type) {
case SDL_QUIT:
d->isRunning = iFalse;
if (findWidget_App("prefs")) {
/* Make sure changed preferences get saved. */
postCommand_App("prefs.dismiss");
processEvents_App(postedEventsOnly_AppEventMode);
}
goto backToMainLoop;
case SDL_APP_LOWMEMORY:
clearCache_App_();
break;
case SDL_APP_WILLENTERFOREGROUND:
postRefresh_App();
break;
case SDL_APP_TERMINATING:
case SDL_APP_WILLENTERBACKGROUND:
savePrefs_App_(d);
saveState_App_(d);
break;
case SDL_DROPFILE: {
iBool wasUsed = processEvent_Window(d->window, &ev);
if (!wasUsed) {
@@ -637,7 +789,7 @@ void processEvents_App(enum iAppEventMode eventMode) {
wasUsed = processEvent_Keys(&ev);
}
if (ev.type == SDL_USEREVENT && ev.user.code == command_UserEventCode) {
-#if defined (iPlatformApple) && !defined (iPlatformIOS)
+#if defined (iPlatformAppleDesktop)
handleCommand_MacOS(command_UserEvent(&ev));
#endif
if (isCommand_UserEvent(&ev, "metrics.changed")) {
@@ -857,6 +1009,20 @@ iMimeHooks *mimeHooks_App(void) {
return app_.mimehooks;
}
+iBool isLandscape_App(void) {
+}
+enum iAppDeviceType deviceType_App(void) {
+#if defined (iPlatformAppleMobile)
+#else
+#endif
+}
iGmCerts *certs_App(void) {
return app_.certs;
}
@@ -875,20 +1041,34 @@ static void updatePrefsThemeButtons_(iWidget *d) {
selected_WidgetFlag,
colorTheme_App() == i);
}
setFlags_Widget(findChild_Widget(d, format_CStr("prefs.accent.%u", i)),
selected_WidgetFlag,
prefs_App()->accent == i);
}
-static void updateColorThemeButton_(iLabelWidget *button, int theme) {
+static void updateDropdownSelection_(iLabelWidget *dropButton, const char *selectedCommand) {
iLabelWidget *item = i.object;
if (!cmp_String(command_LabelWidget(item), command)) {
updateText_LabelWidget(button, text_LabelWidget(item));
break;
const iBool isSelected = endsWith_String(command_LabelWidget(item), selectedCommand);
setFlags_Widget(as_Widget(item), selected_WidgetFlag, isSelected);
if (isSelected) {
updateText_LabelWidget(dropButton, text_LabelWidget(item));
}
}
}
+static void updateColorThemeButton_(iLabelWidget *button, int theme) {
+// const char *mode = strstr(cstr_String(id_Widget(as_Widget(button))), ".dark")
+// ? "dark" : "light";
+}
+static void updateFontButton_(iLabelWidget *button, int font) {
+}
static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
if (equal_Command(cmd, "prefs.dismiss") || equal_Command(cmd, "preferences")) {
setUiScale_Window(get_Window(),
@@ -897,6 +1077,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
postCommandf_App("downloads path:%s",
cstr_String(text_InputWidget(findChild_Widget(d, "prefs.downloads"))));
#endif
postCommandf_App("customframe arg:%d",
isSelected_Widget(findChild_Widget(d, "prefs.customframe")));
postCommandf_App("window.retain arg:%d",
isSelected_Widget(findChild_Widget(d, "prefs.retainwindow")));
postCommandf_App("smoothscroll arg:%d",
@@ -907,6 +1089,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
isSelected_Widget(findChild_Widget(d, "prefs.ostheme")));
postCommandf_App("decodeurls arg:%d",
isSelected_Widget(findChild_Widget(d, "prefs.decodeurls")));
postCommandf_App("searchurl address:%s",
cstr_String(text_InputWidget(findChild_Widget(d, "prefs.searchurl"))));
postCommandf_App("cachesize.set arg:%d",
toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize"))));
postCommandf_App("proxy.gemini address:%s",
@@ -919,6 +1103,7 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
postCommandf_App("prefs.dialogtab arg:%u",
tabPageIndex_Widget(tabs, currentTabPage_Widget(tabs)));
destroy_Widget(d);
postCommand_App("prefs.changed");
return iTrue;
}
else if (equal_Command(cmd, "quoteicon.set")) {
@@ -935,6 +1120,14 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
updateColorThemeButton_(findChild_Widget(d, "prefs.doctheme.light"), arg_Command(cmd));
return iFalse;
}
updateFontButton_(findChild_Widget(d, "prefs.font"), arg_Command(cmd));
return iFalse;
updateFontButton_(findChild_Widget(d, "prefs.headingfont"), arg_Command(cmd));
return iFalse;
else if (equal_Command(cmd, "prefs.ostheme.changed")) {
postCommandf_App("ostheme arg:%d", arg_Command(cmd));
}
@@ -992,33 +1185,6 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe
return doc;
}
-void trimCache_App(void) {
cacheSize += cacheSize_History(history_DocumentWidget(i.object));
iDocumentWidget *doc = i.object;
const size_t pruned = pruneLeastImportant_History(history_DocumentWidget(doc));
if (pruned) {
cacheSize -= pruned;
wasPruned = iTrue;
}
next_ObjectListIterator(&i);
if (!i.value) {
if (!wasPruned) break;
wasPruned = iFalse;
init_ObjectListIterator(&i, docs);
}
-}
static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {
iApp *d = &app_;
if (equal_Command(cmd, "ident.temp.changed")) {
@@ -1092,6 +1258,15 @@ iBool willUseProxy_App(const iRangecc scheme) {
return schemeProxy_App(scheme) != NULL;
}
+const iString *searchQueryUrl_App(const iString *queryStringUnescaped) {
return collectNew_String();
+}
iBool handleCommand_App(const char *cmd) {
iApp *d = &app_;
if (equal_Command(cmd, "config.error")) {
@@ -1100,6 +1275,10 @@ iBool handleCommand_App(const char *cmd) {
suffixPtr_Command(cmd, "where")));
return iTrue;
}
savePrefs_App_(d);
return iTrue;
else if (equal_Command(cmd, "prefs.dialogtab")) {
d->prefs.dialogTab = arg_Command(cmd);
return iTrue;
@@ -1108,8 +1287,24 @@ iBool handleCommand_App(const char *cmd) {
d->prefs.retainWindowSize = arg_Command(cmd);
return iTrue;
}
d->prefs.customFrame = arg_Command(cmd);
return iTrue;
else if (equal_Command(cmd, "window.maximize")) {
SDL_MaximizeWindow(d->window->win);
if (!argLabel_Command(cmd, "toggle")) {
setSnap_Window(d->window, maximized_WindowSnap);
}
else {
setSnap_Window(d->window, snap_Window(d->window) == maximized_WindowSnap ? 0 :
maximized_WindowSnap);
}
return iTrue;
const iBool wasFull = snap_Window(d->window) == fullscreen_WindowSnap;
setSnap_Window(d->window, wasFull ? 0 : fullscreen_WindowSnap);
postCommandf_App("window.fullscreen.changed arg:%d", !wasFull);
return iTrue;
}
else if (equal_Command(cmd, "font.set")) {
@@ -1170,6 +1365,12 @@ iBool handleCommand_App(const char *cmd) {
postCommandf_App("theme.changed auto:%d", isAuto);
return iTrue;
}
d->prefs.accent = arg_Command(cmd);
setThemePalette_Color(d->prefs.theme);
postCommandf_App("theme.changed auto:1");
return iTrue;
else if (equal_Command(cmd, "ostheme")) {
d->prefs.useSystemTheme = arg_Command(cmd);
return iTrue;
@@ -1219,6 +1420,11 @@ iBool handleCommand_App(const char *cmd) {
postRefresh_App();
return iTrue;
}
d->prefs.centerShortDocs = arg_Command(cmd) != 0;
postCommand_App("theme.changed");
return iTrue;
else if (equal_Command(cmd, "prefs.hoverlink.changed")) {
d->prefs.hoverLink = arg_Command(cmd) != 0;
postRefresh_App();
@@ -1241,6 +1447,17 @@ iBool handleCommand_App(const char *cmd) {
}
return iTrue;
}
iString *url = &d->prefs.searchUrl;
setCStr_String(url, suffixPtr_Command(cmd, "address"));
if (startsWith_String(url, "//")) {
prependCStr_String(url, "gemini:");
}
if (!isEmpty_String(url) && !startsWithCase_String(url, "gemini://")) {
prependCStr_String(url, "gemini://");
}
return iTrue;
else if (equal_Command(cmd, "proxy.gemini")) {
setCStr_String(&d->prefs.geminiProxy, suffixPtr_Command(cmd, "address"));
return iTrue;
@@ -1332,7 +1549,13 @@ iBool handleCommand_App(const char *cmd) {
return iTrue;
}
else if (equal_Command(cmd, "tabs.close")) {
iWidget * tabs = findWidget_App("doctabs");
iWidget *tabs = findWidget_App("doctabs");
+#if defined (iPlatformAppleMobile)
/* Can't close the last on mobile. */
if (tabCount_Widget(tabs) == 1) {
return iTrue;
}
+#endif
const iRangecc tabId = range_Command(cmd, "id");
iWidget * doc = !isEmpty_Range(&tabId) ? findWidget_App(cstr_Rangecc(tabId))
: document_App();
@@ -1385,6 +1608,7 @@ iBool handleCommand_App(const char *cmd) {
setToggle_Widget(findChild_Widget(dlg, "prefs.smoothscroll"), d->prefs.smoothScrolling);
setToggle_Widget(findChild_Widget(dlg, "prefs.imageloadscroll"), d->prefs.loadImageInsteadOfScrolling);
setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme);
setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame);
setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize);
setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"),
collectNewFormat_String("%g", uiScale_Window(d->window)));
@@ -1411,8 +1635,11 @@ iBool handleCommand_App(const char *cmd) {
iTrue);
setToggle_Widget(findChild_Widget(dlg, "prefs.biglede"), d->prefs.bigFirstParagraph);
setToggle_Widget(findChild_Widget(dlg, "prefs.sideicon"), d->prefs.sideIcon);
setToggle_Widget(findChild_Widget(dlg, "prefs.centershort"), d->prefs.centerShortDocs);
updateColorThemeButton_(findChild_Widget(dlg, "prefs.doctheme.dark"), d->prefs.docThemeDark);
updateColorThemeButton_(findChild_Widget(dlg, "prefs.doctheme.light"), d->prefs.docThemeLight);
updateFontButton_(findChild_Widget(dlg, "prefs.font"), d->prefs.font);
updateFontButton_(findChild_Widget(dlg, "prefs.headingfont"), d->prefs.headingFont);
setFlags_Widget(
findChild_Widget(
dlg, format_CStr("prefs.saturation.%d", (int) (d->prefs.saturation * 3.99f))),
@@ -1421,6 +1648,7 @@ iBool handleCommand_App(const char *cmd) {
setText_InputWidget(findChild_Widget(dlg, "prefs.cachesize"),
collectNewFormat_String("%d", d->prefs.maxCacheSize));
setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs);
setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.searchUrl);
setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gemini"), &d->prefs.geminiProxy);
setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gopher"), &d->prefs.gopherProxy);
setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.http"), &d->prefs.httpProxy);
@@ -1568,9 +1796,10 @@ void openInDefaultBrowser_App(const iString *url) {
return;
}
#endif
+#if !defined (iPlatformAppleMobile)
iProcess *proc = new_Process();
setArguments_Process(proc,
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
iClob(newStringsCStr_StringList("/usr/bin/env", "open", cstr_String(url), NULL))
#elif defined (iPlatformLinux) || defined (iPlatformOther)
iClob(newStringsCStr_StringList("/usr/bin/env", "xdg-open", cstr_String(url), NULL))
@@ -1584,10 +1813,11 @@ void openInDefaultBrowser_App(const iString *url) {
);
start_Process(proc);
iRelease(proc);
+#endif
}
void revealPath_App(const iString *path) {
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
const char *scriptPath = concatPath_CStr(dataDir_App_(), "revealfile.scpt");
iFile *f = newCStr_File(scriptPath);
if (open_File(f, writeOnly_FileMode | text_FileMode)) {
diff --git a/src/app.h b/src/app.h
index efaf0a3e..9a68c362 100644
--- a/src/app.h
+++ b/src/app.h
@@ -38,6 +38,12 @@ iDeclareType(MimeHooks)
iDeclareType(Visited)
iDeclareType(Window)
+enum iAppDeviceType {
+};
enum iAppEventMode {
waitForNewEvents_AppEventMode,
postedEventsOnly_AppEventMode,
@@ -61,6 +67,9 @@ void refresh_App (void);
iBool isRefreshPending_App (void);
uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */
+iBool isLandscape_App (void);
+iLocalDef iBool isPortrait_App (void) { return !isLandscape_App(); }
+enum iAppDeviceType deviceType_App (void);
iGmCerts * certs_App (void);
iVisited * visited_App (void);
iBookmarks * bookmarks_App (void);
@@ -75,6 +84,8 @@ iBool forceSoftwareRender_App(void);
enum iColorTheme colorTheme_App (void);
const iString * schemeProxy_App (iRangecc scheme);
iBool willUseProxy_App (const iRangecc scheme);
+const iString * searchQueryUrl_App (const iString *queryStringUnescaped);
+const iString * downloadPathForUrl_App(const iString *url, const iString *mime);
typedef void (*iTickerFunc)(iAny *);
diff --git a/src/audio/player.c b/src/audio/player.c
index 1c8538b4..d2ec9870 100644
--- a/src/audio/player.c
+++ b/src/audio/player.c
@@ -566,6 +566,9 @@ static iContentSpec contentSpec_Player_(const iPlayer *d) {
stb_vorbis *vrb = stb_vorbis_open_pushdata(
constData_Block(&d->data->data), size_Block(&d->data->data), &consumed, &error, NULL);
if (!vrb) {
if (error != VORBIS_need_more_data) {
content.type = none_DecoderType;
}
return content;
}
const stb_vorbis_info info = stb_vorbis_get_info(vrb);
@@ -793,8 +796,10 @@ iString *metadataLabel_Player(const iPlayer *d) {
}
unlock_Mutex(&d->decoder->tagMutex);
}
SDL_AUDIO_ISFLOAT(d->decoder->inputFormat) ? "float" : "integer",
d->spec.freq);
appendFormat_String(meta, "%d-bit %s %d Hz", SDL_AUDIO_BITSIZE(d->decoder->inputFormat),
SDL_AUDIO_ISFLOAT(d->decoder->inputFormat) ? "float" : "integer",
d->spec.freq);
return meta;
}
diff --git a/src/bookmarks.c b/src/bookmarks.c
index 1fc24a67..91280f3c 100644
--- a/src/bookmarks.c
+++ b/src/bookmarks.c
@@ -65,8 +65,10 @@ void addTag_Bookmark(iBookmark *d, const char *tag) {
void removeTag_Bookmark(iBookmark *d, const char *tag) {
const size_t pos = indexOfCStr_String(&d->tags, tag);
remove_Block(&d->tags.chars, pos, strlen(tag));
trim_String(&d->tags);
}
iDefineTypeConstruction(Bookmark)
@@ -237,7 +239,7 @@ iBool updateBookmarkIcon_Bookmarks(iBookmarks *d, const iString *url, iChar icon
const uint32_t id = findUrl_Bookmarks(d, url);
if (id) {
iBookmark *bm = get_Bookmarks(d, id);
if (!hasTag_Bookmark(bm, "remote")) {
if (!hasTag_Bookmark(bm, "remote") && !hasTag_Bookmark(bm, "usericon")) {
if (icon != bm->icon) {
bm->icon = icon;
changed = iTrue;
@@ -248,6 +250,37 @@ iBool updateBookmarkIcon_Bookmarks(iBookmarks *d, const iString *url, iChar icon
return changed;
}
+iChar siteIcon_Bookmarks(const iBookmarks *d, const iString *url) {
return 0;
tagPattern_ = new_RegExp("\\busericon\\b", caseSensitive_RegExpOption);
const iBookmark *bm = (const iBookmark *) i.value;
iRegExpMatch m;
init_RegExpMatch(&m);
if (bm->icon && matchString_RegExp(tagPattern_, &bm->tags, &m)) {
const iRangecc bmRoot = urlRoot_String(&bm->url);
if (equalRangeCase_Rangecc(urlRoot, bmRoot)) {
const size_t n = size_String(&bm->url);
if (n < matchingSize) {
matchingSize = n;
icon = bm->icon;
}
}
}
+}
iBookmark *get_Bookmarks(iBookmarks *d, uint32_t id) {
return (iBookmark *) value_Hash(&d->bookmarks, id);
}
diff --git a/src/bookmarks.h b/src/bookmarks.h
index d5182b48..ab9c683b 100644
--- a/src/bookmarks.h
+++ b/src/bookmarks.h
@@ -62,6 +62,7 @@ iBookmark * get_Bookmarks (iBookmarks *, uint32_t id);
void fetchRemote_Bookmarks (iBookmarks *);
void requestFinished_Bookmarks (iBookmarks *, iGmRequest *req);
iBool updateBookmarkIcon_Bookmarks(iBookmarks *, const iString *url, iChar icon);
+iChar siteIcon_Bookmarks (const iBookmarks *, const iString *url);
void save_Bookmarks (const iBookmarks *, const char *dirPath);
uint32_t findUrl_Bookmarks (const iBookmarks *, const iString url); / O(n) */
diff --git a/src/feeds.c b/src/feeds.c
index 3fb05d14..c66b2b84 100644
--- a/src/feeds.c
+++ b/src/feeds.c
@@ -598,7 +598,10 @@ void removeEntries_Feeds(uint32_t feedBookmarkId) {
static int cmpTimeDescending_FeedEntryPtr_(const void *a, const void *b) {
const iFeedEntry * const *e1 = a, * const *e2 = b;
}
const iPtrArray *listEntries_Feeds(void) {
diff --git a/src/gmdocument.c b/src/gmdocument.c
index f73b7dc4..4e76a22a 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "ui/metrics.h"
#include "ui/window.h"
#include "visited.h"
+#include "bookmarks.h"
#include "app.h"
#include <the_Foundation/ptrarray.h>
@@ -254,6 +255,15 @@ static iBool isForcedMonospace_GmDocument_(const iGmDocument *d) {
return iFalse;
}
+static void linkContentLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo *mediaInfo,
uint16_t linkId) {
link->flags |= permanent_GmLinkFlag;
+}
static void doLayout_GmDocument_(iGmDocument *d) {
const iBool isMono = isForcedMonospace_GmDocument_(d);
/* TODO: Collect these parameters into a GmTheme. */
@@ -281,10 +291,10 @@ static void doLayout_GmDocument_(iGmDocument *d) {
5, 10, 5, 10, 0, 0, 0, 5
};
static const float topMargin[max_GmLineType] = {
0.0f, 0.333f, 1.0f, 0.5f, 2.0f, 1.5f, 1.0f, 0.5f
0.0f, 0.333f, 1.0f, 0.5f, 2.0f, 1.5f, 1.0f, 0.25f
};
static const float bottomMargin[max_GmLineType] = {
0.0f, 0.333f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f
0.0f, 0.333f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.25f
};
static const char *arrow = "\u27a4";
static const char *envelope = "\U0001f4e7";
@@ -294,7 +304,6 @@ static void doLayout_GmDocument_(iGmDocument *d) {
static const char *quote = "\u201c";
static const char *magnifyingGlass = "\U0001f50d";
static const char *pointingFinger = "\U0001f449";
const iPrefs *prefs = prefs_App();
clear_Array(&d->layout);
clearLinks_GmDocument_(d);
@@ -325,6 +334,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
iGmRun run = { .color = white_ColorId };
enum iGmLineType type;
int indent = 0;
int rightMargin = 0;
/* Detect the type of the line. */
if (!isPreformat) {
type = lineType_GmDocument_(d, line);
@@ -431,8 +441,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
if ((type == link_GmLineType && prevType == link_GmLineType) ||
(type == quote_GmLineType && prevType == quote_GmLineType)) {
/* No margin between consecutive links/quote lines. */
required =
(type == link_GmLineType ? midRunSkip * lineHeight_Text(paragraph_FontId) : 0);
required = 0;
}
if (isEmpty_Array(&d->layout)) {
required = 0; /* top of document */
@@ -522,17 +531,13 @@ static void doLayout_GmDocument_(iGmDocument *d) {
if (!prefs->quoteIcon && type == quote_GmLineType) {
run.flags |= quoteBorder_GmRunFlag;
}
rightMargin = (type == text_GmLineType || type == bullet_GmLineType ||
type == quote_GmLineType ? 4 : 0);
iAssert(!isEmpty_Range(&runLine)); /* must have something at this point */
while (!isEmpty_Range(&runLine)) {
/* Little bit of breathing space between wrapped lines. */
if ((type == text_GmLineType || type == quote_GmLineType ||
type == bullet_GmLineType) &&
runLine.start != line.start) {
pos.y += midRunSkip * lineHeight_Text(run.font);
}
run.bounds.pos = addX_I2(pos, indent * gap_Text);
const char *contPos;
const int avail = isPreformat ? 0 : (d->size.x - run.bounds.pos.x);
const int avail = isPreformat ? 0 : (d->size.x - run.bounds.pos.x - rightMargin * gap_Text);
const iInt2 dims = tryAdvance_Text(run.font, runLine, avail, &contPos);
iChangeFlags(run.flags, wide_GmRunFlag, (isPreformat && dims.x > d->size.x));
run.bounds.size.x = iMax(avail, dims.x); /* Extends to the right edge for selection. */
@@ -562,48 +567,40 @@ static void doLayout_GmDocument_(iGmDocument *d) {
if (type == link_GmLineType) {
const iMediaId imageId = findLinkImage_Media(d->media, run.linkId);
const iMediaId audioId = !imageId ? findLinkAudio_Media(d->media, run.linkId) : 0;
const iMediaId downloadId = !imageId && !audioId ? findLinkDownload_Media(d->media, run.linkId) : 0;
if (imageId) {
iGmImageInfo img;
iGmMediaInfo img;
imageInfo_Media(d->media, imageId, &img);
/* Mark the link as having content. */ {
iGmLink *link = at_PtrArray(&d->links, run.linkId - 1);
link->flags |= content_GmLinkFlag;
if (img.isPermanent) {
link->flags |= permanent_GmLinkFlag;
}
}
const iInt2 imgSize = imageSize_Media(d->media, imageId);
linkContentLaidOut_GmDocument_(d, &img, run.linkId);
const int margin = lineHeight_Text(paragraph_FontId) / 2;
pos.y += margin;
run.bounds.pos = pos;
run.bounds.size.x = d->size.x;
const float aspect = (float) img.size.y / (float) img.size.x;
const float aspect = (float) imgSize.y / (float) imgSize.x;
run.bounds.size.y = d->size.x * aspect;
run.visBounds = run.bounds;
const iInt2 maxSize = mulf_I2(img.size, get_Window()->pixelRatio);
const iInt2 maxSize = mulf_I2(imgSize, get_Window()->pixelRatio);
if (width_Rect(run.visBounds) > maxSize.x) {
/* Don't scale the image up. */
run.visBounds.size.y = run.visBounds.size.y * maxSize.x / width_Rect(run.visBounds);
run.visBounds.size.y =
run.visBounds.size.y * maxSize.x / width_Rect(run.visBounds);
run.visBounds.size.x = maxSize.x;
run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2;
run.bounds.size.y = run.visBounds.size.y;
run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2;
run.bounds.size.y = run.visBounds.size.y;
}
run.text = iNullRange;
run.font = 0;
run.color = 0;
run.imageId = imageId;
run.text = iNullRange;
run.font = 0;
run.color = 0;
run.mediaType = image_GmRunMediaType;
run.mediaId = imageId;
pushBack_Array(&d->layout, &run);
pos.y += run.bounds.size.y + margin;
}
else if (audioId) {
iGmAudioInfo info;
iGmMediaInfo info;
audioInfo_Media(d->media, audioId, &info);
/* Mark the link as having content. */ {
iGmLink *link = at_PtrArray(&d->links, run.linkId - 1);
link->flags |= content_GmLinkFlag;
if (info.isPermanent) {
link->flags |= permanent_GmLinkFlag;
}
}
linkContentLaidOut_GmDocument_(d, &info, run.linkId);
const int margin = lineHeight_Text(paragraph_FontId) / 2;
pos.y += margin;
run.bounds.pos = pos;
@@ -612,7 +609,25 @@ static void doLayout_GmDocument_(iGmDocument *d) {
run.visBounds = run.bounds;
run.text = iNullRange;
run.color = 0;
run.audioId = audioId;
run.mediaType = audio_GmRunMediaType;
run.mediaId = audioId;
pushBack_Array(&d->layout, &run);
pos.y += run.bounds.size.y + margin;
}
else if (downloadId) {
iGmMediaInfo info;
downloadInfo_Media(d->media, downloadId, &info);
linkContentLaidOut_GmDocument_(d, &info, run.linkId);
const int margin = lineHeight_Text(paragraph_FontId) / 2;
pos.y += margin;
run.bounds.pos = pos;
run.bounds.size.x = d->size.x;
run.bounds.size.y = 2 * lineHeight_Text(uiContent_FontId) + 4 * gap_UI;
run.visBounds = run.bounds;
run.text = iNullRange;
run.color = 0;
run.mediaType = download_GmRunMediaType;
run.mediaId = downloadId;
pushBack_Array(&d->layout, &run);
pos.y += run.bounds.size.y + margin;
}
@@ -719,6 +734,13 @@ static void setDerivedThemeColors_(enum iGmDocumentTheme theme) {
}
}
+static void updateIconBasedOnUrl_GmDocument_(iGmDocument *d) {
d->siteIcon = userIcon;
+}
void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
const iPrefs * prefs = prefs_App();
enum iGmDocumentTheme theme =
@@ -803,8 +825,8 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
set_Color(tmHeading2_ColorId, mix_Color(get_Color(tmBackground_ColorId), get_Color(black_ColorId), 0.67f));
set_Color(tmHeading3_ColorId, mix_Color(get_Color(tmBackground_ColorId), get_Color(black_ColorId), 0.55f));
setHsl_Color(tmBannerBackground_ColorId, addSatLum_HSLColor(base, 0, -0.1f));
setHsl_Color(tmBannerIcon_ColorId, addSatLum_HSLColor(base, 0, -0.2f));
setHsl_Color(tmBannerTitle_ColorId, addSatLum_HSLColor(base, 0, -0.2f));
setHsl_Color(tmBannerIcon_ColorId, addSatLum_HSLColor(base, 0, -0.4f));
setHsl_Color(tmBannerTitle_ColorId, addSatLum_HSLColor(base, 0, -0.4f));
setHsl_Color(tmLinkIcon_ColorId, addSatLum_HSLColor(get_HSLColor(teal_ColorId), 0, 0));
set_Color(tmLinkIconVisited_ColorId, mix_Color(get_Color(tmBackground_ColorId), get_Color(teal_ColorId), 0.35f));
set_Color(tmLinkDomain_ColorId, get_Color(teal_ColorId));
@@ -914,7 +936,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
violet_Hue,
pink_Hue
};
static const float hues[] = { 5, 25, 40, 56, 80, 120, 160, 180, 208, 231, 270, 324 };
static const float hues[] = { 5, 25, 40, 56, 80 + 15, 120, 160, 180, 208, 231, 270, 324 };
static const struct {
int index[2];
} altHues[iElemCount(hues)] = {
@@ -922,7 +944,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
{ 8, 3 }, /* reddish orange */
{ 7, 9 }, /* yellowish orange */
{ 5, 7 }, /* yellow */
{ 11, 2 }, /* greenish yellow */
{ 6, 2 }, /* greenish yellow */
{ 1, 3 }, /* green */
{ 2, 4 }, /* bluish green */
{ 2, 11 }, /* cyan */
@@ -968,6 +990,8 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
setHsl_Color(tmHeading2_ColorId, setLum_HSLColor(altBase, titleLum + 0.70f));
setHsl_Color(tmHeading3_ColorId, setLum_HSLColor(altBase, titleLum + 0.60f));
+// printf("titleLum: %f\n", titleLum);
setHsl_Color(tmParagraph_ColorId, addSatLum_HSLColor(base, 0.1f, 0.6f));
// printf("heading3: %d,%d,%d\n", get_Color(tmHeading3_ColorId).r, get_Color(tmHeading3_ColorId).g, get_Color(tmHeading3_ColorId).b);
@@ -976,11 +1000,8 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
if (delta_Color(get_Color(tmHeading3_ColorId), get_Color(tmParagraph_ColorId)) <= 80) {
/* Smallest headings may be too close to body text color. */
-// iHSLColor clr = get_HSLColor(tmParagraph_ColorId);
-// clr.lum = iMax(0.5f, clr.lum - 0.15f);
//setHsl_Color(tmParagraph_ColorId, clr);
setHsl_Color(tmHeading3_ColorId,
addSatLum_HSLColor(get_HSLColor(tmHeading3_ColorId), 0, 0.15f));
setHsl_Color(tmHeading2_ColorId, addSatLum_HSLColor(get_HSLColor(tmHeading2_ColorId), 0.5f, -0.12f));
setHsl_Color(tmHeading3_ColorId, addSatLum_HSLColor(get_HSLColor(tmHeading3_ColorId), 0.5f, -0.2f));
}
setHsl_Color(tmFirstParagraph_ColorId, addSatLum_HSLColor(base, 0.2f, 0.72f));
@@ -1091,6 +1112,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
if (equal_CStr(cstr_Block(seed), "gemini.circumlunar.space")) {
d->siteIcon = 0x264a; /* gemini symbol */
}
updateIconBasedOnUrl_GmDocument_(d);
}
#if 0
for (int i = tmFirst_ColorId; i < max_ColorId; ++i) {
@@ -1192,6 +1214,7 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) {
iUrl parts;
init_Url(&parts, url);
setRange_String(&d->localHost, parts.host);
}
void setSource_GmDocument(iGmDocument *d, const iString *source, int width) {
diff --git a/src/gmdocument.h b/src/gmdocument.h
index e2c7e10c..16127ea3 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -85,17 +85,24 @@ enum iGmRunFlags {
wide_GmRunFlag = iBit(6), /* horizontally scrollable */
};
+enum iGmRunMediaType {
+};
struct Impl_GmRun {
iRangecc text;
uint8_t font;
uint8_t color;
uint8_t flags;
iRect bounds; /* used for hit testing, may extend to edges */
iRect visBounds; /* actual visual bounds */
uint16_t preId; /* preformatted block ID (sequential) */
iGmLinkId linkId; /* zero for non-links */
};
iDeclareType(GmRunRange)
diff --git a/src/gmrequest.c b/src/gmrequest.c
index 8626403f..0208dc94 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -133,6 +133,7 @@ struct Impl_GmRequest {
iTlsRequest * req;
iGopher gopher;
iGmResponse * resp;
iBool isRespLocked;
iBool isRespFiltered;
iAtomicInt allowUpdate;
@@ -208,7 +209,7 @@ static int processIncomingData_GmRequest_(iGmRequest *d, const iBlock *data) {
resp->statusCode = code;
d->state = receivingBody_GmRequestState;
notifyUpdate = iTrue;
if (willTryFilter_MimeHooks(mimeHooks_App(), &resp->meta)) {
if (d->isFilterEnabled && willTryFilter_MimeHooks(mimeHooks_App(), &resp->meta)) {
d->isRespFiltered = iTrue;
}
}
@@ -226,7 +227,12 @@ static int processIncomingData_GmRequest_(iGmRequest *d, const iBlock *data) {
static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) {
lock_Mutex(d->mtx);
iGmResponse *resp = d->resp;
/* The request has already finished or been aborted (e.g., invalid header). */
delete_Block(readAll_TlsRequest(req));
unlock_Mutex(d->mtx);
return;
iBlock * data = readAll_TlsRequest(req);
const int ubits = processIncomingData_GmRequest_(d, data);
iBool notifyUpdate = (ubits & 1) != 0;
@@ -457,8 +463,9 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host,
void init_GmRequest(iGmRequest *d, iGmCerts *certs) {
d->mtx = new_Mutex();
d->resp = new_GmResponse();
set_Atomic(&d->allowUpdate, iTrue);
init_String(&d->url);
init_Gopher(&d->gopher);
@@ -492,6 +499,10 @@ void deinit_GmRequest(iGmRequest *d) {
delete_Mutex(d->mtx);
}
+void enableFilters_GmRequest(iGmRequest *d, iBool enable) {
+}
void setUrl_GmRequest(iGmRequest *d, const iString *url) {
set_String(&d->url, urlFragmentStripped_String(url));
/* Encode hostname to Punycode here because we want to submit the Punycode domain name
@@ -546,7 +557,7 @@ void submit_GmRequest(iGmRequest *d) {
remove_Block(&path->chars, 0, 1);
}
#endif
iFile * f = new_File(path);
iFile *f = new_File(path);
if (open_File(f, readOnly_FileMode)) {
/* TODO: Check supported file types: images, audio */
/* TODO: Detect text files based on contents? E.g., is the content valid UTF-8. */
diff --git a/src/gmrequest.h b/src/gmrequest.h
index bd340cf1..6d4eb2f8 100644
--- a/src/gmrequest.h
+++ b/src/gmrequest.h
@@ -64,6 +64,7 @@ iDeclareNotifyFunc(GmRequest, Finished)
iDeclareAudienceGetter(GmRequest, updated)
iDeclareAudienceGetter(GmRequest, finished)
+void enableFilters_GmRequest (iGmRequest *, iBool enable);
void setUrl_GmRequest (iGmRequest *, const iString *url);
void submit_GmRequest (iGmRequest *);
void cancel_GmRequest (iGmRequest *);
diff --git a/src/gmutil.c b/src/gmutil.c
index 44bbabfd..2b40367d 100644
--- a/src/gmutil.c
+++ b/src/gmutil.c
@@ -76,6 +76,9 @@ iLocalDef iBool isDef_(iRangecc cc) {
static iRangecc prevPathSeg_(const char *end, const char *start) {
iRangecc seg = { end, end };
return seg;
do {
seg.start--;
} while (*seg.start != '/' && seg.start != start);
@@ -149,6 +152,37 @@ iRangecc urlHost_String(const iString *d) {
return url.host;
}
+iRangecc urlUser_String(const iString *d) {
userPats_[0] = new_RegExp("~([^/?]+)", 0);
userPats_[1] = new_RegExp("/users/([^/?]+)", caseInsensitive_RegExpOption);
if (matchString_RegExp(userPats_[i], d, &m)) {
found = capturedRange_RegExpMatch(&m, 1);
}
+}
+iRangecc urlRoot_String(const iString *d) {
rootEnd = user.end;
iUrl parts;
init_Url(&parts, d);
rootEnd = parts.path.start;
+}
static iBool isAbsolutePath_(iRangecc path) {
return isAbsolute_Path(collect_String(urlDecode_String(collect_String(newRange_String(path)))));
}
@@ -197,7 +231,7 @@ void urlEncodePath_String(iString *d) {
return;
}
iString *encoded = new_String();
iString *path = newRange_String(url.path);
iString *encPath = urlEncodeExclude_String(path, "%/ ");
append_String(encoded, encPath);
@@ -272,6 +306,21 @@ const iString *absoluteUrl_String(const iString *d, const iString *urlMaybeRelat
return absolute;
}
+iBool isLikelyUrl_String(const iString *d) {
not completely per-spec: a) begins with a scheme; b) has something that looks like a
hostname */
"^(//)?([^/?#: ]+)([/?#:].*)$|"
"^(\\w+(\\.\\w+)+|localhost)$",
caseInsensitive_RegExpOption);
+}
static iBool equalPuny_(const iString *d, iRangecc orig) {
if (!endsWith_String(d, "-")) {
return iFalse; /* This is a sufficient condition? */
diff --git a/src/gmutil.h b/src/gmutil.h
index 1caf2445..b2cee61a 100644
--- a/src/gmutil.h
+++ b/src/gmutil.h
@@ -103,7 +103,10 @@ void init_Url (iUrl *, const iString *text);
iRangecc urlScheme_String (const iString *);
iRangecc urlHost_String (const iString *);
+iRangecc urlUser_String (const iString *);
+iRangecc urlRoot_String (const iString *);
const iString * absoluteUrl_String (const iString *, const iString *urlMaybeRelative);
+iBool isLikelyUrl_String (const iString *);
void punyEncodeUrlHost_String(iString *);
void stripDefaultUrlPort_String(iString *);
const iString * urlFragmentStripped_String(const iString *);
diff --git a/src/gopher.c b/src/gopher.c
index 0a7489ba..fa8495d7 100644
--- a/src/gopher.c
+++ b/src/gopher.c
@@ -160,11 +160,11 @@ void open_Gopher(iGopher *d, const iString *url) {
d->type = '0';
}
else if (parts.path.start < parts.path.end) {
d->type = *parts.path.start;
parts.path.start++;
d->type = *parts.path.start;
parts.path.start++;
}
else {
d->type = '1';
d->type = '1';
}
if (d->type == '7' && isEmpty_Range(&parts.query)) {
/* Ask for the query parameters first. */
@@ -204,12 +204,16 @@ void open_Gopher(iGopher *d, const iString *url) {
}
d->isPre = iFalse;
open_Socket(d->socket);
collect_String(urlDecodeExclude_String(collectNewRange_String(parts.path), "\t"));
if (!isEmpty_Range(&parts.query)) {
iAssert(*parts.query.start == '?');
parts.query.start++;
writeData_Socket(d->socket, "\t", 1);
writeData_Socket(d->socket, parts.query.start, size_Range(&parts.query));
const iString *reqQuery =
collect_String(urlDecode_String(collectNewRange_String(parts.query)));
writeData_Socket(d->socket, cstr_String(reqQuery), size_String(reqQuery));
}
writeData_Socket(d->socket, "\r\n", 2);
}
diff --git a/src/history.c b/src/history.c
index 59d515dc..6876d8e3 100644
--- a/src/history.c
+++ b/src/history.c
@@ -299,6 +299,18 @@ size_t cacheSize_History(const iHistory *d) {
return cached;
}
+void clearCache_History(iHistory *d) {
iRecentUrl *url = i.value;
if (url->cachedResponse) {
delete_GmResponse(url->cachedResponse);
url->cachedResponse = NULL;
}
+}
size_t pruneLeastImportant_History(iHistory *d) {
size_t delta = 0;
size_t chosen = iInvalidPos;
diff --git a/src/history.h b/src/history.h
index 7c2684f1..ce3b8e47 100644
--- a/src/history.h
+++ b/src/history.h
@@ -56,6 +56,7 @@ iBool goForward_History (iHistory *);
iRecentUrl *recentUrl_History (iHistory *, size_t pos);
iRecentUrl *mostRecentUrl_History (iHistory *);
iRecentUrl *findUrl_History (iHistory *, const iString *url);
+void clearCache_History (iHistory *);
size_t pruneLeastImportant_History (iHistory *);
const iStringArray * searchContents_History (const iHistory *, const iRegExp pattern); / chronologically ascending */
diff --git a/src/ios.h b/src/ios.h
new file mode 100644
index 00000000..60841aee
--- /dev/null
+++ b/src/ios.h
@@ -0,0 +1,33 @@
+/* Copyright 2021 Jaakko Keränen jaakko.keranen@iki.fi
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+1. Redistributions of source code must retain the above copyright notice, this
+2. Redistributions in binary form must reproduce the above copyright notice,
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+#pragma once
+#include "ui/util.h"
+iDeclareType(Window)
+void setupApplication_iOS (void);
+void setupWindow_iOS (iWindow *window);
+iBool isPhone_iOS (void);
+void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom);
+iBool processEvent_iOS (const SDL_Event *);
diff --git a/src/ios.m b/src/ios.m
new file mode 100644
index 00000000..5abf87df
--- /dev/null
+++ b/src/ios.m
@@ -0,0 +1,105 @@
+/* Copyright 2021 Jaakko Keränen jaakko.keranen@iki.fi
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+1. Redistributions of source code must retain the above copyright notice, this
+2. Redistributions in binary form must reproduce the above copyright notice,
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+#include "ios.h"
+#include "app.h"
+#include "ui/window.h"
+#include <SDL_events.h>
+#include <SDL_syswm.h>
+#import <UIKit/UIKit.h>
+static iBool isSystemDarkMode_ = iFalse;
+static iBool isPhone_ = iFalse;
+static void enableMouse_(iBool yes) {
+}
+void setupApplication_iOS(void) {
isPhone_ = iTrue;
+}
+static UIViewController *viewController_(iWindow *window) {
return wm.info.uikit.window.rootViewController;
+}
+static iBool isDarkMode_(iWindow *window) {
UITraitCollection *traits = ctl.traitCollection;
if (@available(iOS 12.0, *)) {
return (traits.userInterfaceStyle == UIUserInterfaceStyleDark);
}
+}
+void safeAreaInsets_iOS(float *left, float *top, float *right, float *bottom) {
const UIEdgeInsets safe = ctl.view.safeAreaInsets;
*left = safe.left * window->pixelRatio;
*top = safe.top * window->pixelRatio;
*right = safe.right * window->pixelRatio;
*bottom = safe.bottom * window->pixelRatio;
// Fallback on earlier versions
*left = *top = *right = *bottom = 0.0f;
+}
+iBool isPhone_iOS(void) {
+}
+void setupWindow_iOS(iWindow *window) {
+}
+iBool processEvent_iOS(const SDL_Event *ev) {
if (ev->window.event == SDL_WINDOWEVENT_RESTORED) {
const iBool isDark = isDarkMode_(get_Window());
if (isDark != isSystemDarkMode_) {
isSystemDarkMode_ = isDark;
postCommandf_App("~os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0);
}
}
+}
diff --git a/src/main.c b/src/main.c
index 5e4e7284..21c648ba 100644
--- a/src/main.c
+++ b/src/main.c
@@ -22,9 +22,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "app.h"
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
#endif
+#if defined (iPlatformAppleMobile)
+# include "ios.h"
+#endif
#if defined (iPlatformMsys)
@@ -34,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#endif
#include <the_Foundation/commandline.h>
+#include <the_Foundation/tlsrequest.h>
#include <SDL.h>
#include <stdio.h>
#include <signal.h>
@@ -41,7 +45,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
int main(int argc, char **argv) {
printf("Lagrange: A Beautiful Gemini Client\n");
signal(SIGPIPE, SIG_IGN);
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
enableMomentumScroll_MacOS();
registerURLHandler_MacOS();
#endif
@@ -55,6 +59,13 @@ int main(int argc, char **argv) {
mpg123_init();
#endif
init_Foundation();
"ECDHE-ECDSA-CHACHA20-POLY1305:"
"ECDHE-ECDSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES256-GCM-SHA384:"
"ECDHE-RSA-CHACHA20-POLY1305:"
"ECDHE-RSA-AES128-GCM-SHA256");
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
diff --git a/src/media.c b/src/media.c
index 8bd635a5..b329ec03 100644
--- a/src/media.c
+++ b/src/media.c
@@ -27,26 +27,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "audio/player.h"
#include "app.h"
+#include <the_Foundation/file.h>
#include <the_Foundation/ptrarray.h>
#include <stb_image.h>
#include <SDL_hints.h>
#include <SDL_render.h>
+#include <SDL_timer.h>
iDeclareType(GmMediaProps)
struct Impl_GmMediaProps {
iGmLinkId linkId;
iString mime;
iBool isPermanent;
};
static void init_GmMediaProps_(iGmMediaProps *d) {
d->linkId = 0;
init_String(&d->mime);
d->isPermanent = iFalse;
}
static void deinit_GmMediaProps_(iGmMediaProps *d) {
deinit_String(&d->mime);
}
@@ -125,9 +130,77 @@ iDefineTypeConstruction(GmAudio)
/----------------------------------------------------------------------------------------------/
+iDeclareType(GmDownload)
+struct Impl_GmDownload {
+};
+static iBool openFile_GmDownload_(iGmDownload *d) {
return iFalse;
+}
+static void closeFile_GmDownload_(iGmDownload *d) {
+}
+void init_GmDownload(iGmDownload *d) {
+}
+void deinit_GmDownload(iGmDownload *d) {
+}
+static void writeToFile_GmDownload_(iGmDownload *d, const iBlock *data) {
constBegin_Block(data) + d->numBytes,
size_Block(data) - d->numBytes);
const double elapsed = (double) (now - d->rateStartTime) / 1000.0;
d->rateStartTime = now;
d->currentRate = (float) (d->rateNumBytes / elapsed);
d->rateNumBytes = 0;
+}
+iDefineTypeConstruction(GmDownload)
+/----------------------------------------------------------------------------------------------/
struct Impl_Media {
iPtrArray images;
};
iDefineTypeConstruction(Media)
@@ -135,10 +208,12 @@ iDefineTypeConstruction(Media)
void init_Media(iMedia *d) {
init_PtrArray(&d->images);
init_PtrArray(&d->audio);
}
void deinit_Media(iMedia *d) {
clear_Media(d);
deinit_PtrArray(&d->audio);
deinit_PtrArray(&d->images);
}
@@ -152,6 +227,29 @@ void clear_Media(iMedia *d) {
deinit_GmAudio(a.ptr);
}
clear_PtrArray(&d->audio);
deinit_GmDownload(n.ptr);
+}
+iBool setDownloadUrl_Media(iMedia *d, iGmLinkId linkId, const iString *url) {
isNew = iTrue;
dl = new_GmDownload();
dl->props.linkId = linkId;
dl->props.isPermanent = iTrue;
set_String(&dl->props.url, url);
pushBack_PtrArray(&d->downloads, dl);
iGmDownload *dl = at_PtrArray(&d->downloads, existing - 1);
set_String(&dl->props.url, url);
}
iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlock *data,
@@ -195,6 +293,26 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo
}
}
}
iGmDownload *dl;
if (isDeleting) {
take_PtrArray(&d->downloads, existing - 1, (void **) &dl);
delete_GmDownload(dl);
}
else {
dl = at_PtrArray(&d->downloads, existing - 1);
if (isEmpty_String(&dl->props.mime)) {
set_String(&dl->props.mime, mime);
}
if (!dl->file) {
openFile_GmDownload_(dl);
}
writeToFile_GmDownload_(dl, data);
if (!isPartial) {
closeFile_GmDownload_(dl);
}
}
else if (!isDeleting) {
if (startsWith_String(mime, "image/")) {
/* Copy the image to a texture. */
@@ -253,6 +371,24 @@ iMediaId findLinkAudio_Media(const iMedia *d, iGmLinkId linkId) {
return 0;
}
+iMediaId findLinkDownload_Media(const iMedia *d, uint16_t linkId) {
const iGmDownload *dl = i.ptr;
if (dl->props.linkId == linkId) {
return index_PtrArrayConstIterator(&i) + 1;
}
+}
+iInt2 imageSize_Media(const iMedia *d, iMediaId imageId) {
const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1);
return img->size;
+}
SDL_Texture *imageTexture_Media(const iMedia *d, uint16_t imageId) {
if (imageId > 0 && imageId <= size_PtrArray(&d->images)) {
const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1);
@@ -261,12 +397,11 @@ SDL_Texture *imageTexture_Media(const iMedia *d, uint16_t imageId) {
return NULL;
}
-iBool imageInfo_Media(const iMedia *d, iMediaId imageId, iGmImageInfo *info_out) {
+iBool imageInfo_Media(const iMedia *d, iMediaId imageId, iGmMediaInfo *info_out) {
if (imageId > 0 && imageId <= size_PtrArray(&d->images)) {
const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1);
info_out->size = img->size;
info_out->numBytes = img->numBytes;
info_out->mime = cstr_String(&img->props.mime);
info_out->type = cstr_String(&img->props.mime);
info_out->isPermanent = img->props.isPermanent;
return iTrue;
}
@@ -282,10 +417,10 @@ iPlayer *audioData_Media(const iMedia *d, iMediaId audioId) {
return NULL;
}
-iBool audioInfo_Media(const iMedia *d, iMediaId audioId, iGmAudioInfo *info_out) {
+iBool audioInfo_Media(const iMedia *d, iMediaId audioId, iGmMediaInfo *info_out) {
if (audioId > 0 && audioId <= size_PtrArray(&d->audio)) {
const iGmAudio *audio = constAt_PtrArray(&d->audio, audioId - 1);
info_out->mime = cstr_String(&audio->props.mime);
info_out->type = cstr_String(&audio->props.mime);
info_out->isPermanent = audio->props.isPermanent;
return iTrue;
}
@@ -301,6 +436,33 @@ iPlayer *audioPlayer_Media(const iMedia *d, iMediaId audioId) {
return NULL;
}
+iBool downloadInfo_Media(const iMedia *d, iMediaId downloadId, iGmMediaInfo *info_out) {
const iGmDownload *dl = constAt_PtrArray(&d->downloads, downloadId - 1);
info_out->type = cstr_String(&dl->props.mime);
info_out->isPermanent = dl->props.isPermanent;
info_out->numBytes = dl->numBytes;
return iTrue;
+}
+void downloadStats_Media(const iMedia *d, iMediaId downloadId, const iString **path_out,
float *bytesPerSecond_out, iBool *isFinished_out) {
const iGmDownload *dl = constAt_PtrArray(&d->downloads, downloadId - 1);
if (dl->path) {
*path_out = dl->path;
}
*bytesPerSecond_out = dl->currentRate;
*isFinished_out = (dl->path && !dl->file);
+}
/----------------------------------------------------------------------------------------------/
static void updated_MediaRequest_(iAnyObject *obj) {
@@ -313,11 +475,13 @@ static void finished_MediaRequest_(iAnyObject *obj) {
postCommandf_App("media.finished link:%u request:%p", d->linkId, d);
}
-void init_MediaRequest(iMediaRequest *d, iDocumentWidget *doc, unsigned int linkId, const iString *url) {
+void init_MediaRequest(iMediaRequest *d, iDocumentWidget *doc, unsigned int linkId,
const iString *url, iBool enableFilters) {
d->doc = doc;
d->linkId = linkId;
d->req = new_GmRequest(certs_App());
setUrl_GmRequest(d->req, url);
iConnect(GmRequest, d->req, updated, d, updated_MediaRequest_);
iConnect(GmRequest, d->req, finished, d, finished_MediaRequest_);
submit_GmRequest(d->req);
@@ -330,6 +494,7 @@ void deinit_MediaRequest(iMediaRequest *d) {
}
iDefineObjectConstructionArgs(MediaRequest,
(iDocumentWidget *doc, unsigned int linkId, const iString *url),
doc, linkId, url)
(iDocumentWidget *doc, unsigned int linkId, const iString *url,
iBool enableFilters),
doc, linkId, url, enableFilters)
iDefineClass(MediaRequest)
diff --git a/src/media.h b/src/media.h
index ddaa2d3c..7cc941d0 100644
--- a/src/media.h
+++ b/src/media.h
@@ -30,18 +30,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
typedef uint16_t iMediaId;
iDeclareType(Player)
-iDeclareType(GmImageInfo)
-iDeclareType(GmAudioInfo)
+iDeclareType(GmMediaInfo)
-struct Impl_GmImageInfo {
+struct Impl_GmMediaInfo {
size_t numBytes;
-};
-struct Impl_GmAudioInfo {
iBool isPermanent;
};
@@ -53,18 +46,24 @@ enum iMediaFlags {
partialData_MediaFlag = iBit(2),
};
-void clear_Media (iMedia *);
-iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags);
+void clear_Media (iMedia *);
+iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url);
+iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags);
iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId);
-iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmImageInfo *info_out);
+iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out);
+iInt2 imageSize_Media (const iMedia *, iMediaId imageId);
SDL_Texture * imageTexture_Media (const iMedia *, iMediaId imageId);
size_t numAudio_Media (const iMedia *);
iMediaId findLinkAudio_Media (const iMedia *, uint16_t linkId);
-iBool audioInfo_Media (const iMedia *, iMediaId audioId, iGmAudioInfo *info_out);
+iBool audioInfo_Media (const iMedia *, iMediaId audioId, iGmMediaInfo *info_out);
iPlayer * audioPlayer_Media (const iMedia *, iMediaId audioId);
+iMediaId findLinkDownload_Media (const iMedia *, uint16_t linkId);
+iBool downloadInfo_Media (const iMedia *, iMediaId downloadId, iGmMediaInfo *info_out);
+void downloadStats_Media (const iMedia *, iMediaId downloadId, const iString **path_out,
float *bytesPerSecond_out, iBool *isFinished_out);
/----------------------------------------------------------------------------------------------/
@@ -81,4 +80,4 @@ struct Impl_MediaRequest {
};
iDeclareObjectConstructionArgs(MediaRequest, iDocumentWidget *doc, unsigned int linkId,
const iString *url)
const iString *url, iBool enableFilters)
diff --git a/src/mimehooks.c b/src/mimehooks.c
index f4ec6bf4..3b3c0d1a 100644
--- a/src/mimehooks.c
+++ b/src/mimehooks.c
@@ -6,6 +6,7 @@
#include <the_Foundation/path.h>
#include <the_Foundation/process.h>
#include <the_Foundation/stringlist.h>
+#include <the_Foundation/xml.h>
iDefineTypeConstruction(FilterHook)
@@ -69,6 +70,115 @@ iBlock *run_FilterHook_(const iFilterHook *d, const iString *mime, const iBlock
/----------------------------------------------------------------------------------------------/
+static iRegExp *xmlMimePattern_(void) {
xmlMime_ = new_RegExp("(application|text)/(atom\\+)?xml", caseInsensitive_RegExpOption);
+}
+static iBlock *translateAtomXmlToGeminiFeed_(const iString *mime, const iBlock *source,
const iString *requestUrl) {
return NULL;
goto finished;
goto finished;
goto finished;
goto finished;
"20 text/gemini\r\n"
"# %s\n\n", cstr_String(title));
appendFormat_String(&out, "## %s\n\n", cstr_String(subtitle));
"This Atom XML document has been automatically translated to a Gemini feed "
"to allow subscribing to it.\n\n");
iClob(new_RegExp("^([0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9])T.*", caseSensitive_RegExpOption));
iEndCollect();
iBeginCollect();
const iXmlElement *entry = i.ptr;
if (!equal_Rangecc(entry->name, "entry")) {
continue;
}
title = collect_String(decodedContent_XmlElement(child_XmlElement(entry, "title")));
if (isEmpty_String(title)) {
continue;
}
const iString *published =
collect_String(decodedContent_XmlElement(child_XmlElement(entry, "published")));
const iString *updated =
collect_String(decodedContent_XmlElement(child_XmlElement(entry, "updated")));
iRegExpMatch m;
init_RegExpMatch(&m);
if (!matchString_RegExp(datePattern, updated, &m)) {
init_RegExpMatch(&m);
if (!matchString_RegExp(datePattern, published, &m)) {
continue;
}
}
iRangecc url = iNullRange;
iConstForEach(PtrArray, j, &entry->children) {
const iXmlElement *link = j.ptr;
if (!equal_Rangecc(link->name, "link")) {
continue;
}
const iRangecc href = attribute_XmlElement(link, "href");
const iRangecc rel = attribute_XmlElement(link, "rel");
const iRangecc type = attribute_XmlElement(link, "type");
if (startsWithCase_Rangecc(href, "gemini:")) {
url = href;
/* We're happy with the first gemini URL. */
/* TODO: Are we? */
break;
}
url = href;
iUnused(rel, type);
}
if (isEmpty_Range(&url)) {
continue;
}
appendFormat_String(&out, "=> %s %s - %s\n",
cstr_Rangecc(url),
cstr_Rangecc(capturedRange_RegExpMatch(&m, 1)),
cstr_String(title));
+finished:
+}
+/----------------------------------------------------------------------------------------------/
struct Impl_MimeHooks {
iPtrArray filters;
};
@@ -87,7 +197,7 @@ void deinit_MimeHooks(iMimeHooks *d) {
}
iBool willTryFilter_MimeHooks(const iMimeHooks *d, const iString *mime) {
iRegExpMatch m;
iConstForEach(PtrArray, i, &d->filters) {
const iFilterHook *xc = i.ptr;
@@ -96,6 +206,11 @@ iBool willTryFilter_MimeHooks(const iMimeHooks *d, const iString *mime) {
return iTrue;
}
}
return iTrue;
return iFalse;
}
@@ -112,6 +227,14 @@ iBlock *tryFilter_MimeHooks(const iMimeHooks *d, const iString *mime, const iBlo
}
}
}
iBlock *result = translateAtomXmlToGeminiFeed_(mime, body, requestUrl);
if (result) {
return result;
}
return NULL;
}
diff --git a/src/prefs.c b/src/prefs.c
index fe755c63..97ad7f48 100644
--- a/src/prefs.c
+++ b/src/prefs.c
@@ -26,6 +26,8 @@ void init_Prefs(iPrefs *d) {
d->dialogTab = 0;
d->useSystemTheme = iTrue;
d->theme = dark_ColorTheme;
d->retainWindowSize = iTrue;
d->uiScale = 1.0f; /* default set elsewhere */
d->zoomPercent = 100;
@@ -42,6 +44,7 @@ void init_Prefs(iPrefs *d) {
d->lineWidth = 38;
d->bigFirstParagraph = iTrue;
d->quoteIcon = iTrue;
d->docThemeDark = colorfulDark_GmDocumentTheme;
d->docThemeLight = white_GmDocumentTheme;
d->saturation = 1.0f;
@@ -49,9 +52,14 @@ void init_Prefs(iPrefs *d) {
init_String(&d->gopherProxy);
init_String(&d->httpProxy);
init_String(&d->downloadDir);
+#if defined (iPlatformAppleMobile)
+#endif
}
void deinit_Prefs(iPrefs *d) {
deinit_String(&d->geminiProxy);
deinit_String(&d->gopherProxy);
deinit_String(&d->httpProxy);
diff --git a/src/prefs.h b/src/prefs.h
index 1c3274d9..4bbe3ad5 100644
--- a/src/prefs.h
+++ b/src/prefs.h
@@ -38,6 +38,8 @@ struct Impl_Prefs {
/* Window */
iBool useSystemTheme;
enum iColorTheme theme;
iBool retainWindowSize;
float uiScale;
int zoomPercent;
@@ -47,6 +49,7 @@ struct Impl_Prefs {
iBool hoverLink;
iBool smoothScrolling;
iBool loadImageInsteadOfScrolling;
/* Network */
iBool decodeUserVisibleURLs;
int maxCacheSize; /* MB */
@@ -61,6 +64,7 @@ struct Impl_Prefs {
int lineWidth;
iBool bigFirstParagraph;
iBool quoteIcon;
/* Colors */
enum iGmDocumentTheme docThemeDark;
enum iGmDocumentTheme docThemeLight;
diff --git a/src/stb_image_resize.h b/src/stb_image_resize.h
new file mode 100644
index 00000000..42a8efb1
--- /dev/null
+++ b/src/stb_image_resize.h
@@ -0,0 +1,2631 @@
+/* stb_image_resize - v0.96 - public domain image resizing
In one C/C++ file that #includes this file, do this:
#define STB_IMAGE_RESIZE_IMPLEMENTATION
before the #include. That will create the implementation in that file.
stbir_resize_uint8( input_pixels , in_w , in_h , 0,
output_pixels, out_w, out_h, 0, num_channels)
stbir_resize_float(...)
stbir_resize_uint8_srgb( input_pixels , in_w , in_h , 0,
output_pixels, out_w, out_h, 0,
num_channels , alpha_chan , 0)
stbir_resize_uint8_srgb_edgemode(
input_pixels , in_w , in_h , 0,
output_pixels, out_w, out_h, 0,
num_channels , alpha_chan , 0, STBIR_EDGE_CLAMP)
// WRAP/REFLECT/ZERO
See the "header file" section of the source for API documentation.
SRGB & FLOATING POINT REPRESENTATION
The sRGB functions presume IEEE floating point. If you do not have
IEEE floating point, define STBIR_NON_IEEE_FLOAT. This will use
a slower implementation.
MEMORY ALLOCATION
The resize functions here perform a single memory allocation using
malloc. To control the memory allocation, before the #include that
triggers the implementation, do:
#define STBIR_MALLOC(size,context) ...
#define STBIR_FREE(ptr,context) ...
Each resize function makes exactly one call to malloc/free, so to use
temp memory, store the temp memory in the context and return that.
ASSERT
Define STBIR_ASSERT(boolval) to override assert() and not use assert.h
OPTIMIZATION
Define STBIR_SATURATE_INT to compute clamp values in-range using
integer operations instead of float operations. This may be faster
on some platforms.
DEFAULT FILTERS
For functions which don't provide explicit control over what filters
to use, you can change the compile-time defaults with
#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something
#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something
See stbir_filter in the header-file section for the list of filters.
NEW FILTERS
A number of 1D filter kernels are used. For a list of
supported filters see the stbir_filter enum. To add a new filter,
write a filter function and add it to stbir__filter_info_table.
PROGRESS
For interactive use with slow resize operations, you can install
a progress-report callback:
#define STBIR_PROGRESS_REPORT(val) some_func(val)
The parameter val is a float which goes from 0 to 1 as progress is made.
For example:
static void my_progress_report(float progress);
#define STBIR_PROGRESS_REPORT(val) my_progress_report(val)
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize.h"
static void my_progress_report(float progress)
{
printf("Progress: %f%%\n", progress*100);
}
MAX CHANNELS
If your image has more than 64 channels, define STBIR_MAX_CHANNELS
to the max you'll have.
ALPHA CHANNEL
Most of the resizing functions provide the ability to control how
the alpha channel of an image is processed. The important things
to know about this:
1. The best mathematically-behaved version of alpha to use is
called "premultiplied alpha", in which the other color channels
have had the alpha value multiplied in. If you use premultiplied
alpha, linear filtering (such as image resampling done by this
library, or performed in texture units on GPUs) does the "right
thing". While premultiplied alpha is standard in the movie CGI
industry, it is still uncommon in the videogame/real-time world.
If you linearly filter non-premultiplied alpha, strange effects
occur. (For example, the 50/50 average of 99% transparent bright green
and 1% transparent black produces 50% transparent dark green when
non-premultiplied, whereas premultiplied it produces 50%
transparent near-black. The former introduces green energy
that doesn't exist in the source image.)
2. Artists should not edit premultiplied-alpha images; artists
want non-premultiplied alpha images. Thus, art tools generally output
non-premultiplied alpha images.
3. You will get best results in most cases by converting images
to premultiplied alpha before processing them mathematically.
4. If you pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, the
resizer does not do anything special for the alpha channel;
it is resampled identically to other channels. This produces
the correct results for premultiplied-alpha images, but produces
less-than-ideal results for non-premultiplied-alpha images.
5. If you do not pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED,
then the resizer weights the contribution of input pixels
based on their alpha values, or, equivalently, it multiplies
the alpha value into the color channels, resamples, then divides
by the resultant alpha value. Input pixels which have alpha=0 do
not contribute at all to output pixels unless _all_ of the input
pixels affecting that output pixel have alpha=0, in which case
the result for that pixel is the same as it would be without
STBIR_FLAG_ALPHA_PREMULTIPLIED. However, this is only true for
input images in integer formats. For input images in float format,
input pixels with alpha=0 have no effect, and output pixels
which have alpha=0 will be 0 in all channels. (For float images,
you can manually achieve the same result by adding a tiny epsilon
value to the alpha channel of every image, and then subtracting
or clamping it at the end.)
6. You can suppress the behavior described in #5 and make
all-0-alpha pixels have 0 in all channels by #defining
STBIR_NO_ALPHA_EPSILON.
7. You can separately control whether the alpha channel is
interpreted as linear or affected by the colorspace. By default
it is linear; you almost never want to apply the colorspace.
(For example, graphics hardware does not apply sRGB conversion
to the alpha channel.)
Jorge L Rodriguez: Implementation
Sean Barrett: API design, optimizations
Aras Pranckevicius: bugfix
Nathan Reed: warning fixes
0.97 (2020-02-02) fixed warning
0.96 (2019-03-04) fixed warnings
0.95 (2017-07-23) fixed warnings
0.94 (2017-03-18) fixed warnings
0.93 (2017-03-03) fixed bug with certain combinations of heights
0.92 (2017-01-02) fix integer overflow on large (>2GB) images
0.91 (2016-04-02) fix warnings; fix handling of subpixel regions
0.90 (2014-09-17) first released version
See end of file for license information.
Don't decode all of the image data when only processing a partial tile
Don't use full-width decode buffers when only processing a partial tile
When processing wide images, break processing into tiles so data fits in L1 cache
Installable filters?
Resize that respects alpha test coverage
(Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage:
https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp )
+*/
+#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H
+#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H
+#ifdef _MSC_VER
+typedef unsigned char stbir_uint8;
+typedef unsigned short stbir_uint16;
+typedef unsigned int stbir_uint32;
+#else
+#include <stdint.h>
+typedef uint8_t stbir_uint8;
+typedef uint16_t stbir_uint16;
+typedef uint32_t stbir_uint32;
+#endif
+#ifndef STBIRDEF
+#ifdef STB_IMAGE_RESIZE_STATIC
+#define STBIRDEF static
+#else
+#ifdef __cplusplus
+#define STBIRDEF extern "C"
+#else
+#define STBIRDEF extern
+#endif
+#endif
+#endif
+//////////////////////////////////////////////////////////////////////////////
+//
+// Easy-to-use API:
+//
+// * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4)
+// * input_w is input image width (x-axis), input_h is input image height (y-axis)
+// * stride is the offset between successive rows of image data in memory, in bytes. you can
+// specify 0 to mean packed continuously in memory
+// * alpha channel is treated identically to other channels.
+// * colorspace is linear or sRGB as specified by function name
+// * returned result is 1 for success or 0 in case of an error.
+// #define STBIR_ASSERT() to trigger an assert on parameter validation errors.
+// * Memory required grows approximately linearly with input and output size, but with
+// discontinuities at input_w == output_w and input_h == output_h.
+// * These functions use a "default" resampling filter defined at compile time. To change the filter,
+// you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE
+// and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API.
+STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
int num_channels);
+STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
int num_channels);
+// The following functions interpret image data as gamma-corrected sRGB.
+// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel,
+// or otherwise provide the index of the alpha channel. Flags value
+// of 0 will probably do the right thing if you're not sure what
+// the flags mean.
+#define STBIR_ALPHA_CHANNEL_NONE -1
+// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will
+// use alpha-weighted resampling (effectively premultiplying, resampling,
+// then unpremultiplying).
+#define STBIR_FLAG_ALPHA_PREMULTIPLIED (1 << 0)
+// The specified alpha channel should be handled as gamma-corrected value even
+// when doing sRGB operations.
+#define STBIR_FLAG_ALPHA_USES_COLORSPACE (1 << 1)
+STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
int num_channels, int alpha_channel, int flags);
+typedef enum
+{
+} stbir_edge;
+// This function adds the ability to specify how requests to sample off the edge of the image are handled.
+STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_wrap_mode);
+//////////////////////////////////////////////////////////////////////////////
+//
+// Medium-complexity API
+//
+// This extends the easy-to-use API as follows:
+//
+// * Alpha-channel can be processed separately
+// * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE
+// * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT)
+// * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)
+// * Filter can be selected explicitly
+// * uint16 image type
+// * sRGB colorspace available for all types
+// * context parameter for passing to STBIR_MALLOC
+typedef enum
+{
+} stbir_filter;
+typedef enum
+{
+} stbir_colorspace;
+// The following functions are all identical except for the type of the image data
+STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
void *alloc_context);
+STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
void *alloc_context);
+STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
float *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
void *alloc_context);
+//////////////////////////////////////////////////////////////////////////////
+//
+// Full-complexity API
+//
+// This extends the medium API as follows:
+//
+// * uint32 image type
+// * not typesafe
+// * separate filter types for each axis
+// * separate edge modes for each axis
+// * can specify scale explicitly for subpixel correctness
+// * can specify image source tile using texture coordinates
+typedef enum
+{
+} stbir_datatype;
+STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
stbir_datatype datatype,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
stbir_filter filter_horizontal, stbir_filter filter_vertical,
stbir_colorspace space, void *alloc_context);
+STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
stbir_datatype datatype,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
stbir_filter filter_horizontal, stbir_filter filter_vertical,
stbir_colorspace space, void *alloc_context,
float x_scale, float y_scale,
float x_offset, float y_offset);
+STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
stbir_datatype datatype,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
stbir_filter filter_horizontal, stbir_filter filter_vertical,
stbir_colorspace space, void *alloc_context,
float s0, float t0, float s1, float t1);
+// (s0, t0) & (s1, t1) are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use.
+//
+//
+//// end header file /////////////////////////////////////////////////////
+#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H
+#ifdef STB_IMAGE_RESIZE_IMPLEMENTATION
+#ifndef STBIR_ASSERT
+#include <assert.h>
+#define STBIR_ASSERT(x) assert(x)
+#endif
+// For memset
+#include <string.h>
+#include <math.h>
+#ifndef STBIR_MALLOC
+#include <stdlib.h>
+// use comma operator to evaluate c, to avoid "unused parameter" warnings
+#define STBIR_MALLOC(size,c) ((void)(c), malloc(size))
+#define STBIR_FREE(ptr,c) ((void)(c), free(ptr))
+#endif
+#ifndef _MSC_VER
+#ifdef __cplusplus
+#define stbir__inline inline
+#else
+#define stbir__inline
+#endif
+#else
+#define stbir__inline __forceinline
+#endif
+// should produce compiler error if size is wrong
+typedef unsigned char stbir__validate_uint32[sizeof(stbir_uint32) == 4 ? 1 : -1];
+#ifdef _MSC_VER
+#define STBIR__NOTUSED(v) (void)(v)
+#else
+#define STBIR__NOTUSED(v) (void)sizeof(v)
+#endif
+#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0]))
+#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE
+#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM
+#endif
+#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE
+#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL
+#endif
+#ifndef STBIR_PROGRESS_REPORT
+#define STBIR_PROGRESS_REPORT(float_0_to_1)
+#endif
+#ifndef STBIR_MAX_CHANNELS
+#define STBIR_MAX_CHANNELS 64
+#endif
+#if STBIR_MAX_CHANNELS > 65536
+#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536."
+// because we store the indices in 16-bit variables
+#endif
+// This value is added to alpha just before premultiplication to avoid
+// zeroing out color values. It is equivalent to 2^-80. If you don't want
+// that behavior (it may interfere if you have floating point images with
+// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to
+// disable it.
+#ifndef STBIR_ALPHA_EPSILON
+#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20))
+#endif
+#ifdef _MSC_VER
+#define STBIR__UNUSED_PARAM(v) (void)(v)
+#else
+#define STBIR__UNUSED_PARAM(v) (void)sizeof(v)
+#endif
+// must match stbir_datatype
+static unsigned char stbir__type_size[] = {
+};
+// Kernel function centered at 0
+typedef float (stbir__kernel_fn)(float x, float scale);
+typedef float (stbir__support_fn)(float scale);
+typedef struct
+{
+} stbir__filter_info;
+// When upsampling, the contributors are which source pixels contribute.
+// When downsampling, the contributors are which destination pixels are contributed to.
+typedef struct
+{
+} stbir__contributors;
+typedef struct
+{
+} stbir__info;
+static const float stbir__max_uint8_as_float = 255.0f;
+static const float stbir__max_uint16_as_float = 65535.0f;
+static const double stbir__max_uint32_as_float = 4294967295.0;
+static stbir__inline int stbir__min(int a, int b)
+{
+}
+static stbir__inline float stbir__saturate(float x)
+{
return 0;
return 1;
+}
+#ifdef STBIR_SATURATE_INT
+static stbir__inline stbir_uint8 stbir__saturate8(int x)
+{
return x;
return 0;
+}
+static stbir__inline stbir_uint16 stbir__saturate16(int x)
+{
return x;
return 0;
+}
+#endif
+static float stbir__srgb_uchar_to_linear_float[256] = {
+};
+static float stbir__srgb_to_linear(float f)
+{
return f / 12.92f;
return (float)pow((f + 0.055f) / 1.055f, 2.4f);
+}
+static float stbir__linear_to_srgb(float f)
+{
return f * 12.92f;
return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f;
+}
+#ifndef STBIR_NON_IEEE_FLOAT
+// From https://gist.github.com/rygorous/2203834
+typedef union
+{
+} stbir__FP32;
+static const stbir_uint32 fp32_to_srgb8_tab4[104] = {
+};
+static stbir_uint8 stbir__linear_to_srgb_uchar(float in)
+{
in = minval.f;
in = almostone.f;
+}
+#else
+// sRGB transition values, scaled by 1<<28
+static int stbir__srgb_offset_to_linear_scaled[256] =
+{
0, 40738, 122216, 203693, 285170, 366648, 448125, 529603,
611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926,
1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148,
2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856,
3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731,
5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369,
7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021,
10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073,
13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389,
17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552,
21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066,
25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490,
31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568,
36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316,
43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096,
49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700,
57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376,
65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912,
74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648,
83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512,
93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072,
+};
+static stbir_uint8 stbir__linear_to_srgb_uchar(float f)
+{
+}
+#endif
+static float stbir__filter_trapezoid(float x, float scale)
+{
return 0;
float r = 0.5f - halfscale;
if (x <= r)
return 1;
else
return (t - x) / scale;
+}
+static float stbir__support_trapezoid(float scale)
+{
+}
+static float stbir__filter_triangle(float x, float s)
+{
return 1 - x;
return 0;
+}
+static float stbir__filter_cubic(float x, float s)
+{
return (4 + x*x*(3*x - 6))/6;
return (8 + x*(-12 + x*(6 - x)))/6;
+}
+static float stbir__filter_catmullrom(float x, float s)
+{
return 1 - x*x*(2.5f - 1.5f*x);
return 2 - x*(4 + x*(0.5f*x - 2.5f));
+}
+static float stbir__filter_mitchell(float x, float s)
+{
return (16 + x*x*(21 * x - 36))/18;
return (32 + x*(-60 + x*(36 - 7*x)))/18;
+}
+static float stbir__support_zero(float s)
+{
+}
+static float stbir__support_one(float s)
+{
+}
+static float stbir__support_two(float s)
+{
+}
+static stbir__filter_info stbir__filter_info_table[] = {
{ NULL, stbir__support_zero },
{ stbir__filter_trapezoid, stbir__support_trapezoid },
{ stbir__filter_triangle, stbir__support_one },
{ stbir__filter_cubic, stbir__support_two },
{ stbir__filter_catmullrom, stbir__support_two },
{ stbir__filter_mitchell, stbir__support_two },
+};
+stbir__inline static int stbir__use_upsampling(float ratio)
+{
+}
+stbir__inline static int stbir__use_width_upsampling(stbir__info* stbir_info)
+{
+}
+stbir__inline static int stbir__use_height_upsampling(stbir__info* stbir_info)
+{
+}
+// This is the maximum number of input samples that can affect an output sample
+// with the given filter
+static int stbir__get_filter_pixel_width(stbir_filter filter, float scale)
+{
return (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2);
return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale);
+}
+// This is how much to expand buffers to account for filters seeking outside
+// the image boundaries.
+static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale)
+{
+}
+static int stbir__get_coefficient_width(stbir_filter filter, float scale)
+{
return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2);
return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2);
+}
+static int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size)
+{
return output_size;
return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2);
+}
+static int stbir__get_total_horizontal_coefficients(stbir__info* info)
+{
* stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale);
+}
+static int stbir__get_total_vertical_coefficients(stbir__info* info)
+{
* stbir__get_coefficient_width (info->vertical_filter, info->vertical_scale);
+}
+static stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n)
+{
+}
+// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample,
+// if you change it here change it there too.
+static float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c)
+{
+}
+static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max)
+{
return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later
if (n < 0)
return 0;
if (n >= max)
return max - 1;
return n; // NOTREACHED
if (n < 0)
{
if (n < max)
return -n;
else
return max - 1;
}
if (n >= max)
{
int max2 = max * 2;
if (n >= max2)
return 0;
else
return max2 - n - 1;
}
return n; // NOTREACHED
if (n >= 0)
return (n % max);
else
{
int m = (-n) % max;
if (m != 0)
m = max - m;
return (m);
}
// NOTREACHED
STBIR_ASSERT(!"Unimplemented edge type");
return 0;
+}
+stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max)
+{
return n;
+}
+// What input pixels contribute to this output pixel?
+static void stbir__calculate_sample_range_upsample(int n, float out_filter_radius, float scale_ratio, float out_shift, int* in_first_pixel, int* in_last_pixel, float* in_center_of_out)
+{
+}
+// What output pixels does this input pixel contribute to?
+static void stbir__calculate_sample_range_downsample(int n, float in_pixels_radius, float scale_ratio, float out_shift, int* out_first_pixel, int* out_last_pixel, float* out_center_of_in)
+{
+}
+static void stbir__calculate_coefficients_upsample(stbir_filter filter, float scale, int in_first_pixel, int in_last_pixel, float in_center_of_out, stbir__contributors* contributor, float* coefficient_group)
+{
float in_pixel_center = (float)(i + in_first_pixel) + 0.5f;
coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale);
// If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.)
if (i == 0 && !coefficient_group[i])
{
contributor->n0 = ++in_first_pixel;
i--;
continue;
}
total_filter += coefficient_group[i];
coefficient_group[i] *= filter_scale;
if (coefficient_group[i])
break;
// This line has no weight. We can skip it.
contributor->n1 = contributor->n0 + i - 1;
+}
+static void stbir__calculate_coefficients_downsample(stbir_filter filter, float scale_ratio, int out_first_pixel, int out_last_pixel, float out_center_of_in, stbir__contributors* contributor, float* coefficient_group)
+{
STBIR_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical.
float out_pixel_center = (float)(i + out_first_pixel) + 0.5f;
float x = out_pixel_center - out_center_of_in;
coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio;
if (coefficient_group[i])
break;
// This line has no weight. We can skip it.
contributor->n1 = contributor->n0 + i - 1;
+}
+static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, int input_size, int output_size)
+{
float scale;
float total = 0;
for (j = 0; j < num_contributors; j++)
{
if (i >= contributors[j].n0 && i <= contributors[j].n1)
{
float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0);
total += coefficient;
}
else if (i < contributors[j].n0)
break;
}
STBIR_ASSERT(total > 0.9f);
STBIR_ASSERT(total < 1.1f);
scale = 1 / total;
for (j = 0; j < num_contributors; j++)
{
if (i >= contributors[j].n0 && i <= contributors[j].n1)
*stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale;
else if (i < contributors[j].n0)
break;
}
int range, max, width;
skip = 0;
while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0)
skip++;
contributors[j].n0 += skip;
while (contributors[j].n0 < 0)
{
contributors[j].n0++;
skip++;
}
range = contributors[j].n1 - contributors[j].n0 + 1;
max = stbir__min(num_coefficients, range);
width = stbir__get_coefficient_width(filter, scale_ratio);
for (i = 0; i < max; i++)
{
if (i + skip >= width)
break;
*stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip);
}
continue;
contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1);
+}
+// Each scan line uses the same kernel values so we should calculate the kernel
+// values once and then we can use them for every scan line.
+static void stbir__calculate_filters(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size)
+{
float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio;
// Looping through out pixels
for (n = 0; n < total_contributors; n++)
{
float in_center_of_out; // Center of the current out pixel in the in pixel space
int in_first_pixel, in_last_pixel;
stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out);
stbir__calculate_coefficients_upsample(filter, scale_ratio, in_first_pixel, in_last_pixel, in_center_of_out, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0));
}
float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio;
// Looping through in pixels
for (n = 0; n < total_contributors; n++)
{
float out_center_of_in; // Center of the current out pixel in the in pixel space
int out_first_pixel, out_last_pixel;
int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio);
stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in);
stbir__calculate_coefficients_downsample(filter, scale_ratio, out_first_pixel, out_last_pixel, out_center_of_in, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0));
}
stbir__normalize_downsample_coefficients(contributors, coefficients, filter, scale_ratio, input_size, output_size);
+}
+static float* stbir__get_decode_buffer(stbir__info* stbir_info)
+{
+}
+#define STBIR__DECODE(type, colorspace) ((int)(type) * (STBIR_MAX_COLORSPACES) + (int)(colorspace))
+static void stbir__decode_scanline(stbir__info* stbir_info, int n)
+{
for (; x < max_x; x++)
for (c = 0; c < channels; c++)
decode_buffer[x*channels + c] = 0;
return;
for (; x < max_x; x++)
{
int decode_pixel_index = x * channels;
int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
for (c = 0; c < channels; c++)
decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / stbir__max_uint8_as_float;
}
break;
for (; x < max_x; x++)
{
int decode_pixel_index = x * channels;
int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
for (c = 0; c < channels; c++)
decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]];
if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint8_as_float;
}
break;
for (; x < max_x; x++)
{
int decode_pixel_index = x * channels;
int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
for (c = 0; c < channels; c++)
decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float;
}
break;
for (; x < max_x; x++)
{
int decode_pixel_index = x * channels;
int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
for (c = 0; c < channels; c++)
decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float);
if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint16_as_float;
}
break;
for (; x < max_x; x++)
{
int decode_pixel_index = x * channels;
int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
for (c = 0; c < channels; c++)
decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float);
}
break;
for (; x < max_x; x++)
{
int decode_pixel_index = x * channels;
int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
for (c = 0; c < channels; c++)
decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear((float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float));
if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint32_as_float);
}
break;
for (; x < max_x; x++)
{
int decode_pixel_index = x * channels;
int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
for (c = 0; c < channels; c++)
decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c];
}
break;
for (; x < max_x; x++)
{
int decode_pixel_index = x * channels;
int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
for (c = 0; c < channels; c++)
decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]);
if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel];
}
break;
STBIR_ASSERT(!"Unknown type/colorspace/channels combination.");
break;
for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++)
{
int decode_pixel_index = x * channels;
// If the alpha value is 0 it will clobber the color values. Make sure it's not.
float alpha = decode_buffer[decode_pixel_index + alpha_channel];
+#ifndef STBIR_NO_ALPHA_EPSILON
if (stbir_info->type != STBIR_TYPE_FLOAT) {
alpha += STBIR_ALPHA_EPSILON;
decode_buffer[decode_pixel_index + alpha_channel] = alpha;
}
+#endif
for (c = 0; c < channels; c++)
{
if (c == alpha_channel)
continue;
decode_buffer[decode_pixel_index + c] *= alpha;
}
}
for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++)
{
for (c = 0; c < channels; c++)
decode_buffer[x*channels + c] = 0;
}
for (x = input_w; x < max_x; x++)
{
for (c = 0; c < channels; c++)
decode_buffer[x*channels + c] = 0;
}
+}
+static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length)
+{
+}
+static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n)
+{
ring_buffer_index = stbir_info->ring_buffer_begin_index = 0;
stbir_info->ring_buffer_first_scanline = n;
ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries;
STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index);
+}
+static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, float* output_buffer)
+{
int n0 = horizontal_contributors[x].n0;
int n1 = horizontal_contributors[x].n1;
int out_pixel_index = x * channels;
int coefficient_group = coefficient_width * x;
int coefficient_counter = 0;
STBIR_ASSERT(n1 >= n0);
STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin);
STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin);
STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
switch (channels) {
case 1:
for (k = n0; k <= n1; k++)
{
int in_pixel_index = k * 1;
float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
}
break;
case 2:
for (k = n0; k <= n1; k++)
{
int in_pixel_index = k * 2;
float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
}
break;
case 3:
for (k = n0; k <= n1; k++)
{
int in_pixel_index = k * 3;
float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
}
break;
case 4:
for (k = n0; k <= n1; k++)
{
int in_pixel_index = k * 4;
float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient;
}
break;
default:
for (k = n0; k <= n1; k++)
{
int in_pixel_index = k * channels;
float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
int c;
STBIR_ASSERT(coefficient != 0);
for (c = 0; c < channels; c++)
output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
}
break;
}
+}
+static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, float* output_buffer)
+{
case 1:
for (x = 0; x < max_x; x++)
{
int n0 = horizontal_contributors[x].n0;
int n1 = horizontal_contributors[x].n1;
int in_x = x - filter_pixel_margin;
int in_pixel_index = in_x * 1;
int max_n = n1;
int coefficient_group = coefficient_width * x;
for (k = n0; k <= max_n; k++)
{
int out_pixel_index = k * 1;
float coefficient = horizontal_coefficients[coefficient_group + k - n0];
STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
}
}
break;
case 2:
for (x = 0; x < max_x; x++)
{
int n0 = horizontal_contributors[x].n0;
int n1 = horizontal_contributors[x].n1;
int in_x = x - filter_pixel_margin;
int in_pixel_index = in_x * 2;
int max_n = n1;
int coefficient_group = coefficient_width * x;
for (k = n0; k <= max_n; k++)
{
int out_pixel_index = k * 2;
float coefficient = horizontal_coefficients[coefficient_group + k - n0];
STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
}
}
break;
case 3:
for (x = 0; x < max_x; x++)
{
int n0 = horizontal_contributors[x].n0;
int n1 = horizontal_contributors[x].n1;
int in_x = x - filter_pixel_margin;
int in_pixel_index = in_x * 3;
int max_n = n1;
int coefficient_group = coefficient_width * x;
for (k = n0; k <= max_n; k++)
{
int out_pixel_index = k * 3;
float coefficient = horizontal_coefficients[coefficient_group + k - n0];
STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
}
}
break;
case 4:
for (x = 0; x < max_x; x++)
{
int n0 = horizontal_contributors[x].n0;
int n1 = horizontal_contributors[x].n1;
int in_x = x - filter_pixel_margin;
int in_pixel_index = in_x * 4;
int max_n = n1;
int coefficient_group = coefficient_width * x;
for (k = n0; k <= max_n; k++)
{
int out_pixel_index = k * 4;
float coefficient = horizontal_coefficients[coefficient_group + k - n0];
STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient;
}
}
break;
default:
for (x = 0; x < max_x; x++)
{
int n0 = horizontal_contributors[x].n0;
int n1 = horizontal_contributors[x].n1;
int in_x = x - filter_pixel_margin;
int in_pixel_index = in_x * channels;
int max_n = n1;
int coefficient_group = coefficient_width * x;
for (k = n0; k <= max_n; k++)
{
int c;
int out_pixel_index = k * channels;
float coefficient = horizontal_coefficients[coefficient_group + k - n0];
STBIR_ASSERT(coefficient != 0);
for (c = 0; c < channels; c++)
output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
}
}
break;
+}
+static void stbir__decode_and_resample_upsample(stbir__info* stbir_info, int n)
+{
stbir__resample_horizontal_upsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n));
stbir__resample_horizontal_downsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n));
+}
+static void stbir__decode_and_resample_downsample(stbir__info* stbir_info, int n)
+{
stbir__resample_horizontal_upsample(stbir_info, stbir_info->horizontal_buffer);
stbir__resample_horizontal_downsample(stbir_info, stbir_info->horizontal_buffer);
+}
+// Get the specified scan line from the ring buffer.
+static float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_num_entries, int ring_buffer_length)
+{
+}
+static void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode)
+{
for (x=0; x < num_pixels; ++x)
{
int pixel_index = x*channels;
float alpha = encode_buffer[pixel_index + alpha_channel];
float reciprocal_alpha = alpha ? 1.0f / alpha : 0;
// unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb
for (n = 0; n < channels; n++)
if (n != alpha_channel)
encode_buffer[pixel_index + n] *= reciprocal_alpha;
// We added in a small epsilon to prevent the color channel from being deleted with zero alpha.
// Because we only add it for integer types, it will automatically be discarded on integer
// conversion, so we don't need to subtract it back out (which would be problematic for
// numeric precision reasons).
}
if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE))
{
nonalpha[num_nonalpha++] = (stbir_uint16)x;
}
case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR):
for (x=0; x < num_pixels; ++x)
{
int pixel_index = x*channels;
for (n = 0; n < channels; n++)
{
int index = pixel_index + n;
((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]);
}
}
break;
case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB):
for (x=0; x < num_pixels; ++x)
{
int pixel_index = x*channels;
for (n = 0; n < num_nonalpha; n++)
{
int index = pixel_index + nonalpha[n];
((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]);
}
if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE))
((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index+alpha_channel]);
}
break;
case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR):
for (x=0; x < num_pixels; ++x)
{
int pixel_index = x*channels;
for (n = 0; n < channels; n++)
{
int index = pixel_index + n;
((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]);
}
}
break;
case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB):
for (x=0; x < num_pixels; ++x)
{
int pixel_index = x*channels;
for (n = 0; n < num_nonalpha; n++)
{
int index = pixel_index + nonalpha[n];
((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * stbir__max_uint16_as_float);
}
if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]);
}
break;
case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR):
for (x=0; x < num_pixels; ++x)
{
int pixel_index = x*channels;
for (n = 0; n < channels; n++)
{
int index = pixel_index + n;
((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * stbir__max_uint32_as_float);
}
}
break;
case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB):
for (x=0; x < num_pixels; ++x)
{
int pixel_index = x*channels;
for (n = 0; n < num_nonalpha; n++)
{
int index = pixel_index + nonalpha[n];
((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__linear_to_srgb(stbir__saturate(encode_buffer[index]))) * stbir__max_uint32_as_float);
}
if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
((unsigned int*)output_buffer)[pixel_index + alpha_channel] = (unsigned int)STBIR__ROUND_INT(((double)stbir__saturate(encode_buffer[pixel_index + alpha_channel])) * stbir__max_uint32_as_float);
}
break;
case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR):
for (x=0; x < num_pixels; ++x)
{
int pixel_index = x*channels;
for (n = 0; n < channels; n++)
{
int index = pixel_index + n;
((float*)output_buffer)[index] = encode_buffer[index];
}
}
break;
case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB):
for (x=0; x < num_pixels; ++x)
{
int pixel_index = x*channels;
for (n = 0; n < num_nonalpha; n++)
{
int index = pixel_index + nonalpha[n];
((float*)output_buffer)[index] = stbir__linear_to_srgb(encode_buffer[index]);
}
if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
((float*)output_buffer)[pixel_index + alpha_channel] = encode_buffer[pixel_index + alpha_channel];
}
break;
default:
STBIR_ASSERT(!"Unknown type/colorspace/channels combination.");
break;
+}
+static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n)
+{
case 1:
for (k = n0; k <= n1; k++)
{
int coefficient_index = coefficient_counter++;
float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
for (x = 0; x < output_w; ++x)
{
int in_pixel_index = x * 1;
encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
}
}
break;
case 2:
for (k = n0; k <= n1; k++)
{
int coefficient_index = coefficient_counter++;
float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
for (x = 0; x < output_w; ++x)
{
int in_pixel_index = x * 2;
encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
}
}
break;
case 3:
for (k = n0; k <= n1; k++)
{
int coefficient_index = coefficient_counter++;
float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
for (x = 0; x < output_w; ++x)
{
int in_pixel_index = x * 3;
encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient;
}
}
break;
case 4:
for (k = n0; k <= n1; k++)
{
int coefficient_index = coefficient_counter++;
float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
for (x = 0; x < output_w; ++x)
{
int in_pixel_index = x * 4;
encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient;
encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient;
}
}
break;
default:
for (k = n0; k <= n1; k++)
{
int coefficient_index = coefficient_counter++;
float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
for (x = 0; x < output_w; ++x)
{
int in_pixel_index = x * channels;
int c;
for (c = 0; c < channels; c++)
encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient;
}
}
break;
+}
+static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n)
+{
int coefficient_index = k - n0;
int coefficient_group = coefficient_width * contributor;
float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
switch (channels) {
case 1:
for (x = 0; x < output_w; x++)
{
int in_pixel_index = x * 1;
ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
}
break;
case 2:
for (x = 0; x < output_w; x++)
{
int in_pixel_index = x * 2;
ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
}
break;
case 3:
for (x = 0; x < output_w; x++)
{
int in_pixel_index = x * 3;
ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient;
}
break;
case 4:
for (x = 0; x < output_w; x++)
{
int in_pixel_index = x * 4;
ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient;
ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient;
}
break;
default:
for (x = 0; x < output_w; x++)
{
int in_pixel_index = x * channels;
int c;
for (c = 0; c < channels; c++)
ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient;
}
break;
}
+}
+static void stbir__buffer_loop_upsample(stbir__info* stbir_info)
+{
float in_center_of_out = 0; // Center of the current out scanline in the in scanline space
int in_first_scanline = 0, in_last_scanline = 0;
stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out);
STBIR_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries);
if (stbir_info->ring_buffer_begin_index >= 0)
{
// Get rid of whatever we don't need anymore.
while (in_first_scanline > stbir_info->ring_buffer_first_scanline)
{
if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline)
{
// We just popped the last scanline off the ring buffer.
// Reset it to the empty state.
stbir_info->ring_buffer_begin_index = -1;
stbir_info->ring_buffer_first_scanline = 0;
stbir_info->ring_buffer_last_scanline = 0;
break;
}
else
{
stbir_info->ring_buffer_first_scanline++;
stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries;
}
}
}
// Load in new ones.
if (stbir_info->ring_buffer_begin_index < 0)
stbir__decode_and_resample_upsample(stbir_info, in_first_scanline);
while (in_last_scanline > stbir_info->ring_buffer_last_scanline)
stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1);
// Now all buffers should be ready to write a row of vertical sampling.
stbir__resample_vertical_upsample(stbir_info, y);
STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h);
+}
+static void stbir__empty_ring_buffer(stbir__info* stbir_info, int first_necessary_scanline)
+{
// Get rid of whatever we don't need anymore.
while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline)
{
if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h)
{
int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes;
float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length);
stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode);
STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h);
}
if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline)
{
// We just popped the last scanline off the ring buffer.
// Reset it to the empty state.
stbir_info->ring_buffer_begin_index = -1;
stbir_info->ring_buffer_first_scanline = 0;
stbir_info->ring_buffer_last_scanline = 0;
break;
}
else
{
stbir_info->ring_buffer_first_scanline++;
stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries;
}
}
+}
+static void stbir__buffer_loop_downsample(stbir__info* stbir_info)
+{
float out_center_of_in; // Center of the current out scanline in the in scanline space
int out_first_scanline, out_last_scanline;
stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in);
STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries);
if (out_last_scanline < 0 || out_first_scanline >= output_h)
continue;
stbir__empty_ring_buffer(stbir_info, out_first_scanline);
stbir__decode_and_resample_downsample(stbir_info, y);
// Load in new ones.
if (stbir_info->ring_buffer_begin_index < 0)
stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline);
while (out_last_scanline > stbir_info->ring_buffer_last_scanline)
stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1);
// Now the horizontal buffer is ready to write to all ring buffer rows.
stbir__resample_vertical_downsample(stbir_info, y);
+}
+static void stbir__setup(stbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels)
+{
+}
+static void stbir__calculate_transform(stbir__info *info, float s0, float t0, float s1, float t1, float *transform)
+{
info->horizontal_scale = transform[0];
info->vertical_scale = transform[1];
info->horizontal_shift = transform[2];
info->vertical_shift = transform[3];
info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0);
info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0);
info->horizontal_shift = s0 * info->output_w / (s1 - s0);
info->vertical_shift = t0 * info->output_h / (t1 - t0);
+}
+static void stbir__choose_filter(stbir__info *info, stbir_filter h_filter, stbir_filter v_filter)
+{
h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE;
v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE;
+}
+static stbir_uint32 stbir__calculate_memory(stbir__info *info)
+{
// The horizontal buffer is for when we're downsampling the height and we
// can't output the result of sampling the decode buffer directly into the
// ring buffers.
info->horizontal_buffer_size = 0;
// The encode buffer is to retain precision in the height upsampling method
// and isn't used when height downsampling.
info->encode_buffer_size = 0;
+ info->vertical_contributors_size + info->vertical_coefficients_size
+ info->decode_buffer_size + info->horizontal_buffer_size
+ info->ring_buffer_size + info->encode_buffer_size;
+}
+static int stbir__resize_allocated(stbir__info *info,
+{
+#ifdef STBIR_DEBUG_OVERWRITE_TEST
+#define OVERWRITE_ARRAY_SIZE 8
+#endif
return 0;
return 0;
return 0;
flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED;
STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels);
return 0;
return 0;
return 0;
+#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size)
info->horizontal_buffer = NULL;
info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float);
info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float);
STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float);
info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float);
info->encode_buffer = NULL;
STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
+#undef STBIR__NEXT_MEMPTR
stbir__buffer_loop_upsample(info);
stbir__buffer_loop_downsample(info);
+#ifdef STBIR_DEBUG_OVERWRITE_TEST
+#endif
+}
+static int stbir__resize_arbitrary(
+{
return 0;
output_data, output_stride_in_bytes,
alpha_channel, flags, type,
edge_horizontal, edge_vertical,
colorspace, extra_memory, memory_required);
+}
+STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
int num_channels)
+{
output_pixels, output_w, output_h, output_stride_in_bytes,
0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR);
+}
+STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
int num_channels)
+{
output_pixels, output_w, output_h, output_stride_in_bytes,
0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR);
+}
+STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
int num_channels, int alpha_channel, int flags)
+{
output_pixels, output_w, output_h, output_stride_in_bytes,
0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB);
+}
+STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_wrap_mode)
+{
output_pixels, output_w, output_h, output_stride_in_bytes,
0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB);
+}
+STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
void *alloc_context)
+{
output_pixels, output_w, output_h, output_stride_in_bytes,
0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, filter, filter,
edge_wrap_mode, edge_wrap_mode, space);
+}
+STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
void *alloc_context)
+{
output_pixels, output_w, output_h, output_stride_in_bytes,
0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT16, filter, filter,
edge_wrap_mode, edge_wrap_mode, space);
+}
+STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
float *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
void *alloc_context)
+{
output_pixels, output_w, output_h, output_stride_in_bytes,
0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_FLOAT, filter, filter,
edge_wrap_mode, edge_wrap_mode, space);
+}
+STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
stbir_datatype datatype,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
stbir_filter filter_horizontal, stbir_filter filter_vertical,
stbir_colorspace space, void *alloc_context)
+{
output_pixels, output_w, output_h, output_stride_in_bytes,
0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
edge_mode_horizontal, edge_mode_vertical, space);
+}
+STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
stbir_datatype datatype,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
stbir_filter filter_horizontal, stbir_filter filter_vertical,
stbir_colorspace space, void *alloc_context,
float x_scale, float y_scale,
float x_offset, float y_offset)
+{
output_pixels, output_w, output_h, output_stride_in_bytes,
0,0,1,1,transform,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
edge_mode_horizontal, edge_mode_vertical, space);
+}
+STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
stbir_datatype datatype,
int num_channels, int alpha_channel, int flags,
stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
stbir_filter filter_horizontal, stbir_filter filter_vertical,
stbir_colorspace space, void *alloc_context,
float s0, float t0, float s1, float t1)
+{
output_pixels, output_w, output_h, output_stride_in_bytes,
s0,t0,s1,t1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
edge_mode_horizontal, edge_mode_vertical, space);
+}
+#endif // STB_IMAGE_RESIZE_IMPLEMENTATION
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/
diff --git a/src/ui/bindingswidget.c b/src/ui/bindingswidget.c
index 9a2070ba..558bdcd5 100644
--- a/src/ui/bindingswidget.c
+++ b/src/ui/bindingswidget.c
@@ -26,7 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "command.h"
#include "util.h"
#include "app.h"
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
#endif
@@ -140,7 +140,7 @@ static void setActiveItem_BindingsWidget_(iBindingsWidget *d, size_t pos) {
item->isWaitingForEvent = iTrue;
invalidateItem_ListWidget(d->list, d->activePos);
}
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
/* Native menus must be disabled while grabbing keys so the shortcuts don't trigger. */
const iBool enableNativeMenus = (d->activePos == iInvalidPos);
enableMenu_MacOS("Edit", enableNativeMenus);
diff --git a/src/ui/color.c b/src/ui/color.c
index 0227bd3e..51dcdc44 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -21,6 +21,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "color.h"
+#include "app.h"
#include <the_Foundation/string.h>
@@ -52,10 +53,10 @@ static const iColor lightPalette_[] = {
{ 235, 235, 235, 255 },
{ 255, 255, 255, 255 },
{ 255, 255, 32, 255 },
{ 255, 64, 64, 255 },
@@ -71,14 +72,21 @@ iLocalDef void copy_(enum iColorId dst, enum iColorId src) {
}
void setThemePalette_Color(enum iColorTheme theme) {
memcpy(palette_, isDark_ColorTheme(theme) ? darkPalette_ : lightPalette_, sizeof(darkPalette_));
switch (theme) {
case pureBlack_ColorTheme:
case pureBlack_ColorTheme: {
copy_(uiBackground_ColorId, black_ColorId);
copy_(uiBackgroundHover_ColorId, black_ColorId);
copy_(uiBackgroundPressed_ColorId, orange_ColorId);
copy_(uiBackgroundSelected_ColorId, teal_ColorId);
copy_(uiBackgroundFramelessHover_ColorId, teal_ColorId);
copy_(uiBackgroundPressed_ColorId, altAccentHi);
copy_(uiBackgroundSelected_ColorId, accentLo);
copy_(uiBackgroundFramelessHover_ColorId, accentLo);
set_Color(uiBackgroundSidebar_ColorId,
mix_Color(get_Color(black_ColorId), get_Color(gray25_ColorId), 0.55f));
copy_(uiText_ColorId, gray75_ColorId);
copy_(uiTextPressed_ColorId, black_ColorId);
copy_(uiTextStrong_ColorId, white_ColorId);
@@ -86,44 +94,48 @@ void setThemePalette_Color(enum iColorTheme theme) {
copy_(uiTextSelected_ColorId, white_ColorId);
copy_(uiTextFramelessHover_ColorId, white_ColorId);
copy_(uiTextDisabled_ColorId, gray25_ColorId);
copy_(uiTextShortcut_ColorId, cyan_ColorId);
copy_(uiTextAction_ColorId, cyan_ColorId);
copy_(uiTextCaution_ColorId, orange_ColorId);
copy_(uiTextShortcut_ColorId, accentHi);
copy_(uiTextAction_ColorId, accentHi);
copy_(uiTextCaution_ColorId, altAccentHi);
copy_(uiTextAppTitle_ColorId, accentHi);
copy_(uiFrame_ColorId, black_ColorId);
copy_(uiEmboss1_ColorId, gray25_ColorId);
copy_(uiEmboss2_ColorId, black_ColorId);
copy_(uiEmbossHover1_ColorId, cyan_ColorId);
copy_(uiEmbossHover2_ColorId, teal_ColorId);
copy_(uiEmbossPressed1_ColorId, brown_ColorId);
copy_(uiEmbossHover1_ColorId, accentHi);
copy_(uiEmbossHover2_ColorId, accentLo);
copy_(uiEmbossPressed1_ColorId, altAccentLo);
copy_(uiEmbossPressed2_ColorId, gray75_ColorId);
copy_(uiEmbossSelected1_ColorId, cyan_ColorId);
copy_(uiEmbossSelected1_ColorId, accentHi);
copy_(uiEmbossSelected2_ColorId, black_ColorId);
copy_(uiEmbossSelectedHover1_ColorId, white_ColorId);
copy_(uiEmbossSelectedHover2_ColorId, cyan_ColorId);
copy_(uiEmbossSelectedHover2_ColorId, accentHi);
copy_(uiInputBackground_ColorId, black_ColorId);
copy_(uiInputBackgroundFocused_ColorId, black_ColorId);
copy_(uiInputText_ColorId, gray75_ColorId);
copy_(uiInputTextFocused_ColorId, white_ColorId);
copy_(uiInputFrame_ColorId, gray25_ColorId);
copy_(uiInputFrameHover_ColorId, cyan_ColorId);
copy_(uiInputFrameFocused_ColorId, orange_ColorId);
copy_(uiInputCursor_ColorId, orange_ColorId);
copy_(uiInputFrameHover_ColorId, accentHi);
copy_(uiInputFrameFocused_ColorId, altAccentHi);
copy_(uiInputCursor_ColorId, altAccentHi);
copy_(uiInputCursorText_ColorId, black_ColorId);
copy_(uiHeading_ColorId, cyan_ColorId);
copy_(uiAnnotation_ColorId, teal_ColorId);
copy_(uiIcon_ColorId, cyan_ColorId);
copy_(uiIconHover_ColorId, cyan_ColorId);
copy_(uiHeading_ColorId, accentHi);
copy_(uiAnnotation_ColorId, accentLo);
copy_(uiIcon_ColorId, accentHi);
copy_(uiIconHover_ColorId, accentHi);
copy_(uiSeparator_ColorId, gray25_ColorId);
copy_(uiMarked_ColorId, brown_ColorId);
copy_(uiMatching_ColorId, teal_ColorId);
copy_(uiMarked_ColorId, altAccentLo);
copy_(uiMatching_ColorId, accentLo);
break;
}
default:
case dark_ColorTheme:
case dark_ColorTheme: {
copy_(uiBackground_ColorId, gray25_ColorId);
copy_(uiBackgroundHover_ColorId, gray25_ColorId);
copy_(uiBackgroundPressed_ColorId, orange_ColorId);
copy_(uiBackgroundSelected_ColorId, teal_ColorId);
copy_(uiBackgroundFramelessHover_ColorId, teal_ColorId);
copy_(uiBackgroundPressed_ColorId, altAccentHi);
copy_(uiBackgroundSelected_ColorId, accentLo);
copy_(uiBackgroundFramelessHover_ColorId, accentLo);
set_Color(uiBackgroundSidebar_ColorId,
mix_Color(get_Color(black_ColorId), get_Color(gray25_ColorId), 0.75f));
copy_(uiText_ColorId, gray75_ColorId);
copy_(uiTextPressed_ColorId, black_ColorId);
copy_(uiTextStrong_ColorId, white_ColorId);
@@ -131,43 +143,48 @@ void setThemePalette_Color(enum iColorTheme theme) {
copy_(uiTextSelected_ColorId, white_ColorId);
copy_(uiTextDisabled_ColorId, gray50_ColorId);
copy_(uiTextFramelessHover_ColorId, white_ColorId);
copy_(uiTextShortcut_ColorId, cyan_ColorId);
copy_(uiTextAction_ColorId, cyan_ColorId);
copy_(uiTextCaution_ColorId, orange_ColorId);
copy_(uiTextShortcut_ColorId, accentHi);
copy_(uiTextAction_ColorId, accentHi);
copy_(uiTextCaution_ColorId, altAccentHi);
copy_(uiTextAppTitle_ColorId, accentHi);
copy_(uiFrame_ColorId, gray25_ColorId);
copy_(uiEmboss1_ColorId, gray50_ColorId);
copy_(uiEmboss2_ColorId, black_ColorId);
copy_(uiEmbossHover1_ColorId, cyan_ColorId);
copy_(uiEmbossHover2_ColorId, teal_ColorId);
copy_(uiEmbossPressed1_ColorId, brown_ColorId);
copy_(uiEmbossHover1_ColorId, accentHi);
copy_(uiEmbossHover2_ColorId, accentLo);
copy_(uiEmbossPressed1_ColorId, altAccentLo);
copy_(uiEmbossPressed2_ColorId, white_ColorId);
copy_(uiEmbossSelected1_ColorId, cyan_ColorId);
copy_(uiEmbossSelected1_ColorId, accentHi);
copy_(uiEmbossSelected2_ColorId, black_ColorId);
copy_(uiEmbossSelectedHover1_ColorId, white_ColorId);
copy_(uiEmbossSelectedHover2_ColorId, cyan_ColorId);
copy_(uiInputBackground_ColorId, black_ColorId);
copy_(uiEmbossSelectedHover2_ColorId, accentHi);
set_Color(uiInputBackground_ColorId,
mix_Color(get_Color(black_ColorId), get_Color(gray25_ColorId), 0.7f));
copy_(uiInputBackgroundFocused_ColorId, black_ColorId);
copy_(uiInputText_ColorId, gray75_ColorId);
copy_(uiInputTextFocused_ColorId, white_ColorId);
copy_(uiInputFrame_ColorId, gray50_ColorId);
copy_(uiInputFrameHover_ColorId, cyan_ColorId);
copy_(uiInputFrameFocused_ColorId, orange_ColorId);
copy_(uiInputCursor_ColorId, orange_ColorId);
copy_(uiInputFrame_ColorId, uiInputBackground_ColorId);
copy_(uiInputFrameHover_ColorId, accentHi);
copy_(uiInputFrameFocused_ColorId, altAccentHi);
copy_(uiInputCursor_ColorId, altAccentHi);
copy_(uiInputCursorText_ColorId, black_ColorId);
copy_(uiHeading_ColorId, cyan_ColorId);
copy_(uiAnnotation_ColorId, teal_ColorId);
copy_(uiIcon_ColorId, cyan_ColorId);
copy_(uiIconHover_ColorId, cyan_ColorId);
copy_(uiHeading_ColorId, accentHi);
copy_(uiAnnotation_ColorId, accentLo);
copy_(uiIcon_ColorId, accentHi);
copy_(uiIconHover_ColorId, accentHi);
copy_(uiSeparator_ColorId, black_ColorId);
copy_(uiMarked_ColorId, brown_ColorId);
copy_(uiMatching_ColorId, teal_ColorId);
copy_(uiMarked_ColorId, altAccentLo);
copy_(uiMatching_ColorId, accentLo);
break;
}
case light_ColorTheme:
copy_(uiBackground_ColorId, gray75_ColorId);
copy_(uiBackgroundHover_ColorId, gray75_ColorId);
copy_(uiBackgroundSelected_ColorId, orange_ColorId);
copy_(uiBackgroundPressed_ColorId, cyan_ColorId);
copy_(uiBackgroundFramelessHover_ColorId, orange_ColorId);
copy_(uiBackgroundSelected_ColorId, accentHi);
copy_(uiBackgroundPressed_ColorId, altAccentHi);
copy_(uiBackgroundFramelessHover_ColorId, accentHi);
set_Color(uiBackgroundSidebar_ColorId,
mix_Color(get_Color(white_ColorId), get_Color(gray75_ColorId), 0.5f));
copy_(uiText_ColorId, black_ColorId);
copy_(uiTextStrong_ColorId, black_ColorId);
copy_(uiTextDim_ColorId, gray25_ColorId);
@@ -175,9 +192,10 @@ void setThemePalette_Color(enum iColorTheme theme) {
copy_(uiTextSelected_ColorId, black_ColorId);
copy_(uiTextDisabled_ColorId, gray50_ColorId);
copy_(uiTextFramelessHover_ColorId, black_ColorId);
copy_(uiTextShortcut_ColorId, brown_ColorId);
copy_(uiTextAction_ColorId, brown_ColorId);
copy_(uiTextCaution_ColorId, teal_ColorId);
copy_(uiTextShortcut_ColorId, accentLo);
copy_(uiTextAction_ColorId, accentLo);
copy_(uiTextCaution_ColorId, altAccentLo);
copy_(uiTextAppTitle_ColorId, accentLo);
copy_(uiFrame_ColorId, gray50_ColorId);
copy_(uiEmboss1_ColorId, white_ColorId);
copy_(uiEmboss2_ColorId, gray50_ColorId);
@@ -186,32 +204,36 @@ void setThemePalette_Color(enum iColorTheme theme) {
copy_(uiEmbossPressed1_ColorId, black_ColorId);
copy_(uiEmbossPressed2_ColorId, white_ColorId);
copy_(uiEmbossSelected1_ColorId, white_ColorId);
copy_(uiEmbossSelected2_ColorId, brown_ColorId);
copy_(uiEmbossSelectedHover1_ColorId, brown_ColorId);
copy_(uiEmbossSelectedHover2_ColorId, brown_ColorId);
copy_(uiEmbossSelected2_ColorId, accentLo);
copy_(uiEmbossSelectedHover1_ColorId, accentLo);
copy_(uiEmbossSelectedHover2_ColorId, accentLo);
copy_(uiInputBackground_ColorId, white_ColorId);
copy_(uiInputBackgroundFocused_ColorId, white_ColorId);
copy_(uiInputText_ColorId, gray25_ColorId);
copy_(uiInputTextFocused_ColorId, black_ColorId);
copy_(uiInputFrame_ColorId, gray50_ColorId);
copy_(uiInputFrameHover_ColorId, brown_ColorId);
copy_(uiInputFrameFocused_ColorId, teal_ColorId);
copy_(uiInputCursor_ColorId, teal_ColorId);
set_Color(uiInputFrame_ColorId,
mix_Color(get_Color(gray50_ColorId), get_Color(gray75_ColorId), 0.5f));
copy_(uiInputFrameHover_ColorId, accentLo);
copy_(uiInputFrameFocused_ColorId, altAccentLo);
copy_(uiInputCursor_ColorId, altAccentLo);
copy_(uiInputCursorText_ColorId, white_ColorId);
copy_(uiHeading_ColorId, brown_ColorId);
copy_(uiHeading_ColorId, accentLo);
copy_(uiAnnotation_ColorId, gray50_ColorId);
copy_(uiIcon_ColorId, brown_ColorId);
copy_(uiIconHover_ColorId, brown_ColorId);
copy_(uiSeparator_ColorId, gray50_ColorId);
copy_(uiMarked_ColorId, cyan_ColorId);
copy_(uiMatching_ColorId, orange_ColorId);
copy_(uiIcon_ColorId, accentLo);
copy_(uiIconHover_ColorId, accentLo);
set_Color(uiSeparator_ColorId,
mix_Color(get_Color(gray50_ColorId), get_Color(gray75_ColorId), 0.5f));
copy_(uiMarked_ColorId, altAccentHi);
copy_(uiMatching_ColorId, accentHi);
break;
case pureWhite_ColorTheme:
copy_(uiBackground_ColorId, white_ColorId);
copy_(uiBackgroundHover_ColorId, gray75_ColorId);
copy_(uiBackgroundSelected_ColorId, orange_ColorId);
copy_(uiBackgroundPressed_ColorId, cyan_ColorId);
copy_(uiBackgroundFramelessHover_ColorId, orange_ColorId);
copy_(uiBackgroundSelected_ColorId, accentHi);
copy_(uiBackgroundPressed_ColorId, altAccentHi);
copy_(uiBackgroundFramelessHover_ColorId, accentHi);
set_Color(uiBackgroundSidebar_ColorId,
mix_Color(get_Color(white_ColorId), get_Color(gray75_ColorId), 0.5f));
set_Color(uiText_ColorId,
mix_Color(get_Color(black_ColorId), get_Color(gray25_ColorId), 0.5f));
copy_(uiTextPressed_ColorId, black_ColorId);
@@ -220,18 +242,19 @@ void setThemePalette_Color(enum iColorTheme theme) {
copy_(uiTextDim_ColorId, gray25_ColorId);
copy_(uiTextSelected_ColorId, black_ColorId);
copy_(uiTextFramelessHover_ColorId, black_ColorId);
copy_(uiTextShortcut_ColorId, brown_ColorId);
copy_(uiTextAction_ColorId, brown_ColorId);
copy_(uiTextCaution_ColorId, teal_ColorId);
copy_(uiTextShortcut_ColorId, accentLo);
copy_(uiTextAction_ColorId, accentLo);
copy_(uiTextCaution_ColorId, altAccentLo);
copy_(uiTextAppTitle_ColorId, accentLo);
copy_(uiFrame_ColorId, gray75_ColorId);
copy_(uiEmboss1_ColorId, white_ColorId);
copy_(uiEmboss2_ColorId, white_ColorId);
copy_(uiEmbossHover1_ColorId, gray25_ColorId);
copy_(uiEmbossHover2_ColorId, gray25_ColorId);
copy_(uiEmbossPressed1_ColorId, black_ColorId);
copy_(uiEmbossPressed2_ColorId, teal_ColorId);
copy_(uiEmbossPressed2_ColorId, altAccentLo);
copy_(uiEmbossSelected1_ColorId, white_ColorId);
copy_(uiEmbossSelected2_ColorId, brown_ColorId);
copy_(uiEmbossSelected2_ColorId, accentLo);
copy_(uiEmbossSelectedHover1_ColorId, gray50_ColorId);
copy_(uiEmbossSelectedHover2_ColorId, gray50_ColorId);
copy_(uiInputBackground_ColorId, white_ColorId);
@@ -239,31 +262,33 @@ void setThemePalette_Color(enum iColorTheme theme) {
copy_(uiInputText_ColorId, gray25_ColorId);
copy_(uiInputTextFocused_ColorId, black_ColorId);
copy_(uiInputFrame_ColorId, gray50_ColorId);
copy_(uiInputFrameHover_ColorId, brown_ColorId);
copy_(uiInputFrameFocused_ColorId, teal_ColorId);
copy_(uiInputCursor_ColorId, teal_ColorId);
copy_(uiInputFrameHover_ColorId, accentLo);
copy_(uiInputFrameFocused_ColorId, altAccentLo);
copy_(uiInputCursor_ColorId, altAccentLo);
copy_(uiInputCursorText_ColorId, white_ColorId);
copy_(uiHeading_ColorId, brown_ColorId);
copy_(uiHeading_ColorId, accentLo);
copy_(uiAnnotation_ColorId, gray50_ColorId);
copy_(uiIcon_ColorId, brown_ColorId);
copy_(uiIconHover_ColorId, brown_ColorId);
copy_(uiSeparator_ColorId, gray75_ColorId);
copy_(uiMarked_ColorId, cyan_ColorId);
copy_(uiMatching_ColorId, orange_ColorId);
copy_(uiIcon_ColorId, accentLo);
copy_(uiIconHover_ColorId, accentLo);
set_Color(uiSeparator_ColorId,
mix_Color(get_Color(gray50_ColorId), get_Color(gray75_ColorId), 0.67f));
copy_(uiMarked_ColorId, altAccentHi);
copy_(uiMatching_ColorId, accentHi);
break;
}
set_Color(uiSubheading_ColorId,
mix_Color(get_Color(uiText_ColorId),
get_Color(uiIcon_ColorId),
isDark_ColorTheme(theme) ? 0.5f : 0.75f));
get_Color(uiBackgroundSelected_ColorId),
isDark_ColorTheme(theme) ? 0.25f : 0.66f));
mix_Color(get_Color(uiBackground_ColorId),
get_Color(uiBackgroundSelected_ColorId),
theme == pureBlack_ColorTheme ? 0.5f : isDark_ColorTheme(theme) ? 0.25f : 0.66f));
setHsl_Color(uiBackgroundFolder_ColorId,
addSatLum_HSLColor(get_HSLColor(uiBackground_ColorId),
addSatLum_HSLColor(get_HSLColor(uiBackgroundSidebar_ColorId),
0,
theme == pureBlack_ColorTheme ? 0.075
: theme == dark_ColorTheme ? -0.033
theme == pureBlack_ColorTheme ? -1
: theme == dark_ColorTheme ? -0.04
: -0.075));
palette_[uiMarked_ColorId].a = 128;
palette_[uiMatching_ColorId].a = 128;
@@ -423,8 +448,12 @@ const char *escape_Color(int color) {
if (color >= 0 && color < (int) iElemCount(esc)) {
return esc[color];
}
iAssert(color - asciiExtended_ColorEscape + asciiBase_ColorEscape <= 127);
return format_CStr("\r\r%c", color - asciiExtended_ColorEscape + asciiBase_ColorEscape);
}
iHSLColor setSat_HSLColor(iHSLColor d, float sat) {
diff --git a/src/ui/color.h b/src/ui/color.h
index 6849ff2b..8556cd72 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -33,6 +33,12 @@ enum iColorTheme {
max_ColorTheme
};
+enum iColorAccent {
+};
iLocalDef iBool isDark_ColorTheme(enum iColorTheme d) {
return d == pureBlack_ColorTheme || d == dark_ColorTheme;
}
@@ -106,6 +112,8 @@ enum iColorId {
uiBackgroundFolder_ColorId,
uiTextDim_ColorId,
uiSubheading_ColorId,
/* content theme colors */
tmFirst_ColorId,
@@ -172,7 +180,9 @@ iLocalDef iBool isRegularText_ColorId(enum iColorId d) {
#define permanent_ColorId 0x80 /* cannot be changed via escapes */
#define asciiBase_ColorEscape 33
+#define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape)
+#define restore_ColorEscape "\r\x24" /* ASCII Cancel */
#define black_ColorEscape "\r!"
#define gray25_ColorEscape "\r""
#define gray50_ColorEscape "\r#"
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index ef8d5ff3..947d9d5f 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -41,7 +41,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "labelwidget.h"
#include "media.h"
#include "paint.h"
-#include "playerui.h"
+#include "mediaui.h"
#include "scrollwidget.h"
#include "util.h"
#include "visbuf.h"
@@ -67,14 +67,49 @@ iDeclareType(PersistentDocumentState)
iDeclareTypeConstruction(PersistentDocumentState)
iDeclareTypeSerialization(PersistentDocumentState)
+enum iReloadInterval {
+};
+static int seconds_ReloadInterval_(enum iReloadInterval d) {
+}
+static const char *label_ReloadInterval_(enum iReloadInterval d) {
"Never",
"1 minute",
"5 minutes",
"15 minutes",
"1 hour",
"4 hours",
"12 hours",
"Once per day"
+}
struct Impl_PersistentDocumentState {
iHistory *history;
iString * url;
};
void init_PersistentDocumentState(iPersistentDocumentState *d) {
}
void deinit_PersistentDocumentState(iPersistentDocumentState *d) {
@@ -84,7 +119,7 @@ void deinit_PersistentDocumentState(iPersistentDocumentState *d) {
void serialize_PersistentDocumentState(const iPersistentDocumentState *d, iStream *outs) {
serialize_String(d->url, outs);
serialize_History(d->history, outs);
}
@@ -94,7 +129,8 @@ void deserialize_PersistentDocumentState(iPersistentDocumentState *d, iStream *i
/* Oopsie, this should not have been written; invalid URL. */
clear_String(d->url);
}
deserialize_History(d->history, ins);
}
@@ -102,17 +138,7 @@ iDefineTypeConstruction(PersistentDocumentState)
/----------------------------------------------------------------------------------------------/
-iDeclareType(OutlineItem)
-struct Impl_OutlineItem {
-};
-/----------------------------------------------------------------------------------------------/
-static void animatePlayers_DocumentWidget_ (iDocumentWidget *d);
+static void animateMedia_DocumentWidget_ (iDocumentWidget *d);
static void updateSideIconBuf_DocumentWidget_ (iDocumentWidget *d);
static const int smoothDuration_DocumentWidget_ = 600; /* milliseconds */
@@ -133,6 +159,7 @@ enum iDocumentWidgetFlag {
showLinkNumbers_DocumentWidgetFlag = iBit(3),
setHoverViaKeys_DocumentWidgetFlag = iBit(4),
newTabViaHomeKeys_DocumentWidgetFlag = iBit(5),
};
enum iDocumentLinkOrdinalMode {
@@ -151,6 +178,7 @@ struct Impl_DocumentWidget {
iGmRequest * request;
iAtomicInt isRequestUpdated; /* request has new content, need to parse it */
iObjectList * media;
iString sourceHeader;
iString sourceMime;
iBlock sourceContent; /* original content as received, for saving */
@@ -170,10 +198,10 @@ struct Impl_DocumentWidget {
iAnim animWideRunOffset;
uint16_t animWideRunId;
iGmRunRange animWideRunRange;
const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */
float grabbedStartVolume;
const iGmRun * hoverLink;
const iGmRun * contextLink;
const iGmRun * firstVisibleRun;
@@ -183,8 +211,6 @@ struct Impl_DocumentWidget {
float initNormScrollY;
iAnim scrollY;
iAnim sideOpacity;
iScrollWidget *scroll;
iWidget * menu;
iWidget * playerMenu;
@@ -228,9 +254,8 @@ void init_DocumentWidget(iDocumentWidget *d) {
d->lastVisibleRun = NULL;
d->visBuf = new_VisBuf();
d->invalidRuns = new_PtrSet();
init_Anim(&d->sideOpacity, 0);
init_String(&d->sourceHeader);
init_String(&d->sourceMime);
init_Block(&d->sourceContent, 0);
@@ -238,9 +263,9 @@ void init_DocumentWidget(iDocumentWidget *d) {
init_PtrArray(&d->visibleLinks);
init_PtrArray(&d->visibleWideRuns);
init_Array(&d->wideRunOffsets, sizeof(int));
d->grabbedPlayer = NULL;
init_String(&d->pendingGotoHeading);
init_Click(&d->click, d, SDL_BUTTON_LEFT);
addChild_Widget(w, iClob(d->scroll = new_ScrollWidget()));
@@ -251,7 +276,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
addChildFlags_Widget(w,
iClob(new_IndicatorWidget()),
resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag);
-#if !defined (iPlatformApple) /* in system menu */
+#if !defined (iPlatformAppleDesktop) /* in system menu */
addAction_Widget(w, reload_KeyShortcut, "navigate.reload");
addAction_Widget(w, closeTab_KeyShortcut, "tabs.close");
addAction_Widget(w, SDLK_d, KMOD_PRIMARY, "bookmark.add");
@@ -270,7 +295,6 @@ void deinit_DocumentWidget(iDocumentWidget *d) {
delete_TextBuf(d->timestampBuf);
delete_VisBuf(d->visBuf);
delete_PtrSet(d->invalidRuns);
iRelease(d->media);
iRelease(d->request);
deinit_String(&d->pendingGotoHeading);
@@ -278,11 +302,11 @@ void deinit_DocumentWidget(iDocumentWidget *d) {
deinit_String(&d->sourceMime);
deinit_String(&d->sourceHeader);
iRelease(d->doc);
SDL_RemoveTimer(d->playerTimer);
SDL_RemoveTimer(d->mediaTimer);
}
deinit_Array(&d->wideRunOffsets);
deinit_PtrArray(&d->visibleWideRuns);
deinit_PtrArray(&d->visibleLinks);
delete_Block(d->certFingerprint);
@@ -312,10 +336,14 @@ static void requestFinished_DocumentWidget_(iAnyObject *obj) {
}
static int documentWidth_DocumentWidget_(const iDocumentWidget *d) {
-2.0f, 10.0f); /* adapt to width */
fontSize_UI * prefs->lineWidth * prefs->zoomPercent / 100);
}
@@ -332,15 +360,17 @@ static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
rect.pos.y += margin;
rect.size.y -= margin;
}
/* Center vertically if short. There is one empty paragraph line's worth of margin
between the banner and the page contents. */
const int bannerHeight = banner ? height_Rect(banner->visBounds) : 0;
int offset = iMax(0, (rect.size.y + margin - docSize.y - bannerHeight -
lineHeight_Text(paragraph_FontId)) / 2);
rect.pos.y += offset;
rect.size.y = docSize.y;
const iInt2 docSize = size_GmDocument(d->doc);
if (docSize.y < rect.size.y) {
/* Center vertically if short. There is one empty paragraph line's worth of margin
between the banner and the page contents. */
const int bannerHeight = banner ? height_Rect(banner->visBounds) : 0;
int offset = iMax(0, (rect.size.y + margin - docSize.y - bannerHeight -
lineHeight_Text(paragraph_FontId)) / 2);
rect.pos.y += offset;
rect.size.y = docSize.y;
}
}
return rect;
}
@@ -369,7 +399,7 @@ static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) {
static void addVisible_DocumentWidget_(void *context, const iGmRun *run) {
iDocumentWidget *d = context;
if (!d->firstVisibleRun) {
d->firstVisibleRun = run;
}
@@ -378,8 +408,9 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) {
if (run->preId && run->flags & wide_GmRunFlag) {
pushBack_PtrArray(&d->visibleWideRuns, run);
}
pushBack_PtrArray(&d->visiblePlayers, run);
iAssert(run->mediaId);
pushBack_PtrArray(&d->visibleMedia, run);
}
if (run->linkId) {
pushBack_PtrArray(&d->visibleLinks, run);
@@ -488,7 +519,7 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
static void animate_DocumentWidget_(void *ticker) {
iDocumentWidget *d = ticker;
addTicker_App(animate_DocumentWidget_, d);
}
}
@@ -503,71 +534,66 @@ static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d, iBool isAnimat
animate_DocumentWidget_(d);
}
-static void updateOutlineOpacity_DocumentWidget_(iDocumentWidget *d) {
setValue_Anim(&d->outlineOpacity, 0.0f, 0);
return;
opacity = 1.0f;
-}
-static uint32_t playerUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {
+static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {
if (document_App() != d) {
return 0;
}
const iGmRun *run = i.ptr;
iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), run->audioId);
if (flags_Player(plr) & adjustingVolume_PlayerFlag ||
(isStarted_Player(plr) && !isPaused_Player(plr))) {
interval = 1000 / 15;
if (run->mediaType == audio_GmRunMediaType) {
iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId);
if (flags_Player(plr) & adjustingVolume_PlayerFlag ||
(isStarted_Player(plr) && !isPaused_Player(plr))) {
interval = iMin(interval, 1000 / 15);
}
}
else if (run->mediaType == download_GmRunMediaType) {
interval = iMin(interval, 1000);
}
}
}
-static uint32_t postPlayerUpdate_DocumentWidget_(uint32_t interval, void *context) {
+static uint32_t postMediaUpdate_DocumentWidget_(uint32_t interval, void *context) {
/* Called in timer thread; don't access the widget. */
iUnused(context);
postCommand_App("media.player.update");
return interval;
}
-static void updatePlayers_DocumentWidget_(iDocumentWidget *d) {
+static void updateMedia_DocumentWidget_(iDocumentWidget *d) {
if (document_App() == d) {
refresh_Widget(d);
iConstForEach(PtrArray, i, &d->visiblePlayers) {
iConstForEach(PtrArray, i, &d->visibleMedia) {
const iGmRun *run = i.ptr;
iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), run->audioId);
if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag &&
flags_Player(plr) & adjustingVolume_PlayerFlag) {
setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse);
if (run->mediaType == audio_GmRunMediaType) {
iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId);
if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag &&
flags_Player(plr) & adjustingVolume_PlayerFlag) {
setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse);
}
}
}
}
SDL_RemoveTimer(d->playerTimer);
d->playerTimer = 0;
SDL_RemoveTimer(d->mediaTimer);
d->mediaTimer = 0;
}
}
-static void animatePlayers_DocumentWidget_(iDocumentWidget *d) {
+static void animateMedia_DocumentWidget_(iDocumentWidget *d) {
if (document_App() != d) {
if (d->playerTimer) {
SDL_RemoveTimer(d->playerTimer);
d->playerTimer = 0;
if (d->mediaTimer) {
SDL_RemoveTimer(d->mediaTimer);
d->mediaTimer = 0;
}
return;
}
d->playerTimer = SDL_AddTimer(interval, postPlayerUpdate_DocumentWidget_, d);
d->mediaTimer = SDL_AddTimer(interval, postMediaUpdate_DocumentWidget_, d);
}
}
@@ -590,6 +616,10 @@ static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) {
}
static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
centerVertically_DocumentWidgetFlag,
prefs_App()->centerShortDocs || startsWithCase_String(d->mod.url, "about:") ||
!isSuccess_GmStatusCode(d->sourceStatus));
const iRangei visRange = visibleRange_DocumentWidget_(d);
const iRect bounds = bounds_Widget(as_Widget(d));
setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_DocumentWidget_(d) });
@@ -599,7 +629,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0);
clear_PtrArray(&d->visibleLinks);
clear_PtrArray(&d->visibleWideRuns);
const iRangecc oldHeading = currentHeading_DocumentWidget_(d);
/* Scan for visible runs. */ {
d->firstVisibleRun = NULL;
@@ -611,7 +641,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
}
updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window()));
updateSideOpacity_DocumentWidget_(d, iTrue);
/* Remember scroll positions of recently visited pages. */ {
iRecentUrl *recent = mostRecentUrl_History(d->mod.history);
if (recent && docSize && d->state == ready_RequestState) {
@@ -637,7 +667,9 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
iUrl parts;
init_Url(&parts, d->mod.url);
if (equalCase_Rangecc(parts.scheme, "about")) {
pushBackCStr_StringArray(title, "Lagrange");
if (!findWidget_App("winbar")) {
pushBackCStr_StringArray(title, "Lagrange");
}
}
else if (!isEmpty_Range(&parts.host)) {
pushBackRange_StringArray(title, parts.host);
@@ -659,9 +691,10 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
const iChar siteIcon = siteIcon_GmDocument(d->doc);
if (siteIcon) {
if (!isEmpty_String(text)) {
prependCStr_String(text, " ");
prependCStr_String(text, " " restore_ColorEscape);
}
prependChar_String(text, siteIcon);
prependCStr_String(text, escape_Color(uiIcon_ColorId));
}
const int width = advanceRange_Text(default_FontId, range_String(text)).x;
if (width <= avail ||
@@ -703,69 +736,27 @@ static void invalidate_DocumentWidget_(iDocumentWidget *d) {
clear_PtrSet(d->invalidRuns);
}
-static int outlineWidth_DocumentWidget_(const iDocumentWidget *d) {
(width_Rect(bounds) - docWidth) / 2 - gap_Text * d->pageMargin - gap_UI * d->pageMargin
- 2 * outlinePadding_DocumentWidget_ * gap_UI;
return outlineMinWidth_DocumentWdiget_ * gap_UI;
-}
static iRangecc bannerText_DocumentWidget_(const iDocumentWidget *d) {
return isEmpty_String(d->titleUser) ? range_String(bannerText_GmDocument(d->doc))
: range_String(d->titleUser);
}
-static void updateOutline_DocumentWidget_(iDocumentWidget *d) {
return;
return; /* Too short */
-// const iRangecc topText = urlHost_String(d->mod.url);
-// iInt2 size = advanceWrapRange_Text(uiContent_FontId, outWidth, topText);
-// pushBack_Array(&d->outline, &(iOutlineItem){ topText, uiContent_FontId, (iRect){ pos, size },
-// tmBannerTitle_ColorId, none_ColorId });
-// pos.y += size.y;
const iGmHeading *head = i.value;
const int indent = head->level * 5 * gap_UI;
size = advanceWrapRange_Text(uiLabel_FontId, outWidth - indent, head->text);
if (head->level == 0) {
pos.y += gap_UI * 1.5f;
}
pushBack_Array(
&d->outline,
&(iOutlineItem){ head->text, uiLabel_FontId, (iRect){ addX_I2(pos, indent), size } });
pos.y += size.y;
-}
-static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) {
+static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) {
d->foundMark = iNullRange;
d->selectMark = iNullRange;
d->hoverLink = NULL;
d->contextLink = NULL;
d->firstVisibleRun = NULL;
d->lastVisibleRun = NULL;
+}
+static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) {
updateWindowTitle_DocumentWidget_(d);
updateVisible_DocumentWidget_(d);
updateSideIconBuf_DocumentWidget_(d);
invalidate_DocumentWidget_(d);
refresh_Widget(as_Widget(d));
}
@@ -1001,7 +992,7 @@ static void updateTrust_DocumentWidget_(iDocumentWidget *d, const iGmResponse *r
else if (d->certFlags & trusted_GmCertFlag) {
updateTextCStr_LabelWidget(lock, green_ColorEscape closedLock_CStr);
}
updateTextCStr_LabelWidget(lock, isDarkMode ? orange_ColorEscape "\u26a0"
: black_ColorEscape "\u26a0");
}
@@ -1009,17 +1000,7 @@ static void updateTrust_DocumentWidget_(iDocumentWidget *d, const iGmResponse *r
}
static void parseUser_DocumentWidget_(iDocumentWidget *d) {
new_RegExp("/users/([^/?]+)", caseInsensitive_RegExpOption) };
if (matchString_RegExp(userPats[i], d->mod.url, &m)) {
setRange_String(d->titleUser, capturedRange_RegExpMatch(&m, 1));
}
iRelease(userPats[i]);
}
static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
@@ -1034,6 +1015,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
/* Use the cached response data. */
updateTrust_DocumentWidget_(d, resp);
d->sourceTime = resp->when;
d->sourceStatus = success_GmStatusCode;
format_String(&d->sourceHeader, "(cached content)");
updateTimestampBuf_DocumentWidget_(d);
set_Block(&d->sourceContent, &resp->body);
@@ -1042,7 +1024,6 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
d->state = ready_RequestState;
updateSideOpacity_DocumentWidget_(d, iFalse);
updateSideIconBuf_DocumentWidget_(d);
updateOutline_DocumentWidget_(d);
updateVisible_DocumentWidget_(d);
postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
return iTrue;
@@ -1109,6 +1090,9 @@ static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) {
}
static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) {
documentY += d->pageMargin * gap_UI;
init_Anim(&d->scrollY,
documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2
: lineHeight_Text(paragraph_FontId)));
@@ -1188,6 +1172,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
updateTrust_DocumentWidget_(d, resp);
init_Anim(&d->sideOpacity, 0);
format_String(&d->sourceHeader, "%d %s", statusCode, get_GmError(statusCode)->title);
d->sourceStatus = statusCode;
switch (category_GmStatusCode(statusCode)) {
case categoryInput_GmStatusCode: {
iUrl parts;
@@ -1323,16 +1308,20 @@ static iMediaRequest *findMediaRequest_DocumentWidget_(const iDocumentWidget *d,
return NULL;
}
-static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId) {
+static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId, iBool enableFilters) {
if (!findMediaRequest_DocumentWidget_(d, linkId)) {
const iString *imageUrl = absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->doc, linkId));
pushBack_ObjectList(d->media, iClob(new_MediaRequest(d, linkId, imageUrl)));
const iString *mediaUrl = absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->doc, linkId));
pushBack_ObjectList(d->media, iClob(new_MediaRequest(d, linkId, mediaUrl, enableFilters)));
invalidate_DocumentWidget_(d);
return iTrue;
}
return iFalse;
}
+static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) {
+}
static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
iMediaRequest *req = pointerLabel_Command(cmd, "request");
iBool isOurRequest = iFalse;
@@ -1351,7 +1340,8 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *
const enum iGmStatusCode code = status_GmRequest(req->req);
if (isSuccess_GmStatusCode(code)) {
iGmResponse *resp = lockResponse_GmRequest(req->req);
if (startsWith_String(&resp->meta, "audio/")) {
if (isDownloadRequest_DocumentWidget(d, req) ||
startsWith_String(&resp->meta, "audio/")) {
/* TODO: Use a helper? This is same as below except for the partialData flag. */
if (setData_Media(media_GmDocument(d->doc),
req->linkId,
@@ -1375,7 +1365,8 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *
const enum iGmStatusCode code = status_GmRequest(req->req);
/* Give the media to the document for presentation. */
if (isSuccess_GmStatusCode(code)) {
if (startsWith_String(meta_GmRequest(req->req), "image/") ||
if (isDownloadRequest_DocumentWidget(d, req) ||
startsWith_String(meta_GmRequest(req->req), "image/") ||
startsWith_String(meta_GmRequest(req->req), "audio/")) {
setData_Media(media_GmDocument(d->doc),
req->linkId,
@@ -1413,12 +1404,13 @@ static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) {
static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) {
iConstForEach(PtrArray, i, &d->visibleLinks) {
const iGmRun *run = i.ptr;
if (run->linkId && !run->imageId && ~run->flags & decoration_GmRunFlag) {
if (run->linkId && run->mediaType == none_GmRunMediaType &&
~run->flags & decoration_GmRunFlag) {
const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId);
if (isMediaLink_GmDocument(d->doc, run->linkId) &&
linkFlags & imageFileExtension_GmLinkFlag &&
~linkFlags & content_GmLinkFlag && ~linkFlags & permanent_GmLinkFlag ) {
if (requestMedia_DocumentWidget_(d, run->linkId)) {
if (requestMedia_DocumentWidget_(d, run->linkId, iTrue)) {
return iTrue;
}
}
@@ -1428,57 +1420,7 @@ static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) {
}
static void saveToDownloads_(const iString *url, const iString *mime, const iBlock *content) {
parts.path.start++;
parts.path.end--;
if (!isEmpty_Range(&parts.host)) {
setRange_String(name, parts.host);
replace_Block(&name->chars, '.', '_');
}
iRangecc fn = { parts.path.start + lastIndexOfCStr_Rangecc(parts.path, "/") + 1,
parts.path.end };
if (!isEmpty_Range(&fn)) {
setRange_String(name, fn);
}
/* This would be interpreted as a reference to a home directory. */
remove_Block(&name->chars, 0, 1);
/* No extension specified in URL. */
if (startsWith_String(mime, "text/gemini")) {
appendCStr_String(savePath, ".gmi");
}
else if (startsWith_String(mime, "text/")) {
appendCStr_String(savePath, ".txt");
}
else if (startsWith_String(mime, "image/")) {
appendCStr_String(savePath, cstr_String(mime) + 6);
}
/* Make it unique. */
iDate now;
initCurrent_Date(&now);
size_t insPos = lastIndexOfCStr_String(savePath, ".");
if (insPos == iInvalidPos) {
insPos = size_String(savePath);
}
const iString *date = collect_String(format_Date(&now, "_%Y-%m-%d_%H%M%S"));
insertData_Block(&savePath->chars, insPos, cstr_String(date), size_String(date));
/* Write the file. */ {
iFile *f = new_File(savePath);
if (open_File(f, writeOnly_FileMode)) {
@@ -1496,7 +1438,6 @@ static void saveToDownloads_(const iString *url, const iString *mime, const iBlo
}
iRelease(f);
}
}
static void addAllLinks_(void *context, const iGmRun *run) {
@@ -1534,30 +1475,45 @@ static const int homeRowKeys_[] = {
't', 'y',
};
+static void updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumentWidget *d,
iBool keepCenter) {
of the visible area fixed. */
/* Keep the first visible run visible at the same position. */
/* TODO: First *fully* visible run? */
voffset = visibleRange_DocumentWidget_(d).start - top_Rect(run->visBounds);
run = findRunAtLoc_GmDocument(d->doc, runLoc);
if (run) {
scrollTo_DocumentWidget_(d,
top_Rect(run->visBounds) +
lineHeight_Text(paragraph_FontId) + voffset,
iFalse);
}
run = findRunAtLoc_GmDocument(d->doc, runLoc);
if (run) {
scrollTo_DocumentWidget_(d, mid_Rect(run->bounds).y, iTrue);
}
+}
static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
iWidget *w = as_Widget(d);
if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) {
const iBool isVerticalOnly =
!argLabel_Command(cmd, "horiz") && argLabel_Command(cmd, "vert");
/* Alt/Option key may be involved in window size changes. */
iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);
if (isVerticalOnly) {
scroll_DocumentWidget_(d, 0); /* prevent overscroll */
}
else {
const iGmRun *mid = middleRun_DocumentWidget_(d);
const char *midLoc = (mid ? mid->text.start : NULL);
setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d));
scroll_DocumentWidget_(d, 0);
if (midLoc) {
mid = findRunAtLoc_GmDocument(d->doc, midLoc);
if (mid) {
scrollTo_DocumentWidget_(d, mid_Rect(mid->bounds).y, iTrue);
}
}
}
const iBool keepCenter = equal_Command(cmd, "font.changed");
updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, keepCenter);
updateSideIconBuf_DocumentWidget_(d);
updateOutline_DocumentWidget_(d);
invalidate_DocumentWidget_(d);
dealloc_VisBuf(d->visBuf);
updateWindowTitle_DocumentWidget_(d);
@@ -1572,11 +1528,11 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
return iFalse;
}
else if (equal_Command(cmd, "window.mouse.exited")) {
updateOutlineOpacity_DocumentWidget_(d);
return iFalse;
}
else if (equal_Command(cmd, "theme.changed") && document_App() == d) {
updateTheme_DocumentWidget_(d);
updateVisible_DocumentWidget_(d);
updateTrust_DocumentWidget_(d, NULL);
updateSideIconBuf_DocumentWidget_(d);
invalidate_DocumentWidget_(d);
@@ -1596,10 +1552,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
}
init_Anim(&d->sideOpacity, 0);
updateSideOpacity_DocumentWidget_(d, iFalse);
updateOutlineOpacity_DocumentWidget_(d);
updateWindowTitle_DocumentWidget_(d);
allocVisBuffer_DocumentWidget_(d);
animatePlayers_DocumentWidget_(d);
animateMedia_DocumentWidget_(d);
return iFalse;
}
else if (equal_Command(cmd, "tab.created")) {
@@ -1608,10 +1563,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
return iFalse;
}
else if (equal_Command(cmd, "document.info") && d == document_App()) {
const char *unchecked = red_ColorEscape "\u2610";
const char *checked = green_ColorEscape "\u2611";
const char *actionLabels[] = { uiTextCaution_ColorEscape "Trust", "Copy Fingerprint", "Dismiss" };
const char *actionCmds[] = { "server.trustcert", "server.copycert", "message.ok" };
const char *unchecked = red_ColorEscape "\u2610";
const char *checked = green_ColorEscape "\u2611";
const iBool haveFingerprint = (d->certFlags & haveFingerprint_GmCertFlag) != 0;
const iBool canTrust =
(d->certFlags == (available_GmCertFlag | haveFingerprint_GmCertFlag |
@@ -1655,15 +1608,29 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
uiText_ColorEscape,
d->certFlags & trusted_GmCertFlag ? "Trusted" : "Not trusted");
setFocus_Widget(NULL);
iArray *items = new_Array(sizeof(iMenuItem));
if (canTrust) {
pushBack_Array(
items, &(iMenuItem){ uiTextCaution_ColorEscape "Trust", 0, 0, "server.trustcert" });
}
if (haveFingerprint) {
pushBack_Array(items, &(iMenuItem){ "Copy Fingerprint", 0, 0, "server.copycert" });
}
if (!isEmpty_Array(items)) {
pushBack_Array(items, &(iMenuItem){ "---", 0, 0, 0 });
}
pushBack_Array(items, &(iMenuItem){ "Dismiss", 0, 0, "message.ok" });
iWidget *dlg = makeQuestion_Widget(uiHeading_ColorEscape "PAGE INFORMATION",
cstr_String(msg),
actionLabels + (canTrust ? 0 : haveFingerprint ? 1 : 2),
actionCmds + (canTrust ? 0 : haveFingerprint ? 1 : 2),
canTrust ? 3 : haveFingerprint ? 2 : 1);
data_Array(items),
size_Array(items));
delete_Array(items);
/* Enforce a minimum size. */
iWidget *sizer = new_Widget();
setSize_Widget(sizer, init_I2(gap_UI * 90, 1));
addChildFlags_Widget(dlg, iClob(sizer), frameless_WidgetFlag);
setFlags_Widget(dlg, centerHorizontal_WidgetFlag, iFalse);
setPos_Widget(dlg, bottomLeft_Rect(bounds_Widget(findWidget_App("navbar.lock"))));
arrange_Widget(dlg);
addAction_Widget(dlg, SDLK_ESCAPE, 0, "message.ok");
addAction_Widget(dlg, SDLK_SPACE, 0, "message.ok");
@@ -1673,7 +1640,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
const iRangecc host = urlHost_String(d->mod.url);
if (!isEmpty_Block(d->certFingerprint) && !isEmpty_Range(&host)) {
setTrusted_GmCerts(certs_App(), host, d->certFingerprint, &d->certExpiry);
d->certFlags |= trusted_GmCertFlag;
d->certFlags |= trusted_GmCertFlag;
postCommand_App("document.info");
updateTrust_DocumentWidget_(d, NULL);
redoLayout_GmDocument(d->doc);
@@ -1713,6 +1680,19 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
}
return iTrue;
}
if (d->contextLink) {
const iGmLinkId linkId = d->contextLink->linkId;
setDownloadUrl_Media(
media_GmDocument(d->doc), linkId, linkUrl_GmDocument(d->doc, linkId));
requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */);
redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */
updateVisible_DocumentWidget_(d);
invalidate_DocumentWidget_(d);
refresh_Widget(w);
}
return iTrue;
else if (equal_Command(cmd, "document.input.submit") && document_Command(cmd) == d) {
iString *value = suffix_Command(cmd, "value");
set_String(value, collect_String(urlEncode_String(value)));
@@ -1769,7 +1749,6 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
iReleasePtr(&d->request);
updateVisible_DocumentWidget_(d);
updateSideIconBuf_DocumentWidget_(d);
updateOutline_DocumentWidget_(d);
postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
/* Check for a pending goto. */
if (!isEmpty_String(&d->pendingGotoHeading)) {
@@ -1794,7 +1773,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
}
}
else if (equal_Command(cmd, "media.player.update")) {
updatePlayers_DocumentWidget_(d);
updateMedia_DocumentWidget_(d);
return iFalse;
}
else if (equal_Command(cmd, "document.stop") && document_App() == d) {
@@ -1828,7 +1807,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
}
return iTrue;
}
d->initNormScrollY = normScrollPos_DocumentWidget_(d);
fetch_DocumentWidget_(d);
return iTrue;
@@ -1904,11 +1883,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
return iTrue;
}
else if (equal_Command(cmd, "navigate.root") && document_App() == d) {
iUrl parts;
init_Url(&parts, d->mod.url);
postCommandf_App(
"open url:%s/",
cstr_Rangecc((iRangecc){ constBegin_String(d->mod.url), parts.path.start }));
postCommandf_App("open url:%s/", cstr_Rangecc(urlRoot_String(d->mod.url)));
return iTrue;
}
else if (equalWidget_Command(cmd, w, "scroll.moved")) {
@@ -2033,10 +2008,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
makeQuestion_Widget(
uiHeading_ColorEscape "IMPORT BOOKMARKS",
format_CStr("Found %d new link%s on the page.", size_PtrArray(links), plural),
(const char *[]){ "Cancel",
format_CStr(uiTextAction_ColorEscape "Add %d Bookmark%s",
size_PtrArray(links), plural) },
(const char *[]){ "cancel", "bookmark.links" },
(iMenuItem[]){ { "Cancel", 0, 0, NULL },
{ format_CStr(uiTextAction_ColorEscape "Add %d Bookmark%s",
size_PtrArray(links),
plural), 0, 0, "bookmark.links" } },
2);
}
else {
@@ -2060,24 +2035,45 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
else if (equalWidget_Command(cmd, w, "menu.closed")) {
updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window()));
}
if (d->mod.reloadInterval) {
if (!isValid_Time(&d->sourceTime) || elapsedSeconds_Time(&d->sourceTime) >=
seconds_ReloadInterval_(d->mod.reloadInterval)) {
postCommand_Widget(w, "document.reload");
}
}
iWidget *dlg = makeQuestion_Widget(uiTextAction_ColorEscape "AUTO-RELOAD",
"Select the auto-reload interval for this tab.",
(iMenuItem[]){ { "Cancel", 0, 0, NULL } },
1);
for (int i = 0; i < max_ReloadInterval; ++i) {
insertChildAfterFlags_Widget(
dlg,
iClob(new_LabelWidget(label_ReloadInterval_(i),
format_CStr("document.autoreload.set arg:%d", i))),
i + 1,
resizeToParentWidth_WidgetFlag |
((int) d->mod.reloadInterval == i ? selected_WidgetFlag : 0));
}
arrange_Widget(dlg);
return iTrue;
d->mod.reloadInterval = arg_Command(cmd);
return iFalse;
}
-#if 0
-static int outlineHeight_DocumentWidget_(const iDocumentWidget *d) {
-}
-#endif
-static iRect playerRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) {
+static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) {
const iRect docBounds = documentBounds_DocumentWidget_(d);
return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), -value_Anim(&d->scrollY)));
}
static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) {
iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->audioId);
iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId);
setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue);
d->grabbedStartVolume = volume_Player(plr);
d->grabbedPlayer = run;
@@ -2085,7 +2081,7 @@ static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *r
}
else if (d->grabbedPlayer) {
setFlags_Player(
audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->audioId),
audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId),
volumeGrabbed_PlayerFlag,
iFalse);
d->grabbedPlayer = NULL;
@@ -2096,7 +2092,7 @@ static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *r
}
}
-static iBool processPlayerEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) {
+static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) {
if (ev->type != SDL_MOUSEBUTTONDOWN && ev->type != SDL_MOUSEBUTTONUP &&
ev->type != SDL_MOUSEMOTION) {
return iFalse;
@@ -2111,10 +2107,13 @@ static iBool processPlayerEvents_DocumentWidget_(iDocumentWidget *d, const SDL_E
return iFalse;
}
const iInt2 mouse = init_I2(ev->button.x, ev->button.y);
const iGmRun *run = i.ptr;
const iRect rect = playerRect_DocumentWidget_(d, run);
iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), run->audioId);
if (run->mediaType != audio_GmRunMediaType) {
continue;
}
const iRect rect = runRect_DocumentWidget_(d, run);
iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId);
if (contains_Rect(rect, mouse)) {
iPlayerUI ui;
init_PlayerUI(&ui, plr, rect);
@@ -2135,7 +2134,7 @@ static iBool processPlayerEvents_DocumentWidget_(iDocumentWidget *d, const SDL_E
}
if (contains_Rect(ui.playPauseRect, mouse)) {
setPaused_Player(plr, !isPaused_Player(plr));
animatePlayers_DocumentWidget_(d);
animateMedia_DocumentWidget_(d);
return iTrue;
}
else if (contains_Rect(ui.rewindRect, mouse)) {
@@ -2151,7 +2150,7 @@ static iBool processPlayerEvents_DocumentWidget_(iDocumentWidget *d, const SDL_E
setFlags_Player(plr,
adjustingVolume_PlayerFlag,
!(flags_Player(plr) & adjustingVolume_PlayerFlag));
animatePlayers_DocumentWidget_(d);
animateMedia_DocumentWidget_(d);
refresh_Widget(d);
return iTrue;
}
@@ -2313,6 +2312,8 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
}
}
else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) {
/* TODO: Maybe clean this up a bit? Wheel events are used for scrolling
but they are calculated differently based on device/mouse/trackpad. */
const iInt2 mouseCoord = mouseCoord_Window(get_Window());
#if defined (iPlatformApple)
/* On macOS, we handle both trackpad and mouse events. We expect SDL to identify
@@ -2320,6 +2321,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
if (ev->wheel.which == 0) { /* Trackpad with precise scrolling w/inertia. */
stop_Anim(&d->scrollY);
iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y);
+# if defined (iPlatformAppleMobile)
wheel.x = -wheel.x;
+# else
/* Wheel mounts are in points. */
mulfv_I2(&wheel, get_Window()->pixelRatio);
/* Only scroll on one axis at a time. */
if (iAbs(wheel.x) > iAbs(wheel.y)) {
wheel.y = 0;
@@ -2327,8 +2333,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
else {
wheel.x = 0;
}
scroll_DocumentWidget_(d, -wheel.y * get_Window()->pixelRatio);
scrollWideBlock_DocumentWidget_(d, mouseCoord, wheel.x * get_Window()->pixelRatio, 0);
+# endif
scroll_DocumentWidget_(d, -wheel.y);
scrollWideBlock_DocumentWidget_(d, mouseCoord, wheel.x, 0);
}
else
#endif
@@ -2372,7 +2379,6 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
else {
updateHover_DocumentWidget_(d, mpos);
}
updateOutlineOpacity_DocumentWidget_(d);
}
if (ev->type == SDL_MOUSEBUTTONDOWN) {
if (ev->button.button == SDL_BUTTON_X1) {
@@ -2384,7 +2390,8 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
return iTrue;
}
if (ev->button.button == SDL_BUTTON_MIDDLE && d->hoverLink) {
postCommandf_App("open newtab:1 url:%s",
postCommandf_App("open newtab:%d url:%s",
SDL_GetModState() & KMOD_SHIFT ? 1 : 2,
cstr_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId)));
return iTrue;
}
@@ -2399,11 +2406,14 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
init_Array(&items, sizeof(iMenuItem));
if (d->contextLink) {
const iString *linkUrl = linkUrl_GmDocument(d->doc, d->contextLink->linkId);
const int linkFlags = linkFlags_GmDocument(d->doc, d->contextLink->linkId);
const iRangecc scheme = urlScheme_String(linkUrl);
const iBool isGemini = equalCase_Rangecc(scheme, "gemini");
iBool isNative = iFalse;
if (willUseProxy_App(scheme) || isGemini ||
equalCase_Rangecc(scheme, "finger") ||
equalCase_Rangecc(scheme, "gopher")) {
isNative = iTrue;
/* Regular links that we can open. */
pushBackN_Array(
&items,
@@ -2447,10 +2457,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
0,
format_CStr("!bookmark.add title:%s url:%s",
cstr_String(linkLabel),
cstr_String(linkUrl)) } },
cstr_String(linkUrl)) },
},
3);
if (isNative && d->contextLink->mediaType != download_GmRunMediaType) {
pushBackN_Array(&items, (iMenuItem[]){
{ "---", 0, 0, NULL },
{ "Download Linked File", 0, 0, "document.downloadlink" },
}, 2);
}
iMediaRequest *mediaReq;
if ((mediaReq = findMediaRequest_DocumentWidget_(d, d->contextLink->linkId)) != NULL) {
if ((mediaReq = findMediaRequest_DocumentWidget_(d, d->contextLink->linkId)) != NULL &&
d->contextLink->mediaType != download_GmRunMediaType) {
if (isFinished_GmRequest(mediaReq->req)) {
pushBack_Array(&items,
&(iMenuItem){ "Save to Downloads",
@@ -2477,6 +2495,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
{ "Go to Root", navigateRoot_KeyShortcut, "navigate.root" },
{ "---", 0, 0, NULL },
{ "Reload Page", reload_KeyShortcut, "navigate.reload" },
{ "Set Auto-Reload...", 0, 0, "document.autoreload.menu" },
{ "---", 0, 0, NULL },
{ "Bookmark Page...", SDLK_d, KMOD_PRIMARY, "bookmark.add" },
{ "Subscribe to Page...", subscribeToPage_KeyModifier, "feeds.subscribe" },
@@ -2500,7 +2519,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
processContextMenuEvent_Widget(d->menu, ev, {});
}
}
return iTrue;
}
/* The left mouse button. */
@@ -2511,9 +2530,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
case drag_ClickResult: {
if (d->grabbedPlayer) {
iPlayer *plr =
audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->audioId);
audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId);
iPlayerUI ui;
init_PlayerUI(&ui, plr, playerRect_DocumentWidget_(d, d->grabbedPlayer));
init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer));
float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider);
setVolume_Player(plr, d->grabbedStartVolume + off);
refresh_Widget(w);
@@ -2557,13 +2576,13 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
const int linkFlags = linkFlags_GmDocument(d->doc, linkId);
iAssert(linkId);
/* Media links are opened inline by default. */
if (isMediaLink_GmDocument(d->doc, linkId)) {
if (isMediaLink_GmDocument(d->doc, linkId)) {
if (linkFlags & content_GmLinkFlag && linkFlags & permanent_GmLinkFlag) {
/* We have the content and it cannot be dismissed, so nothing
further to do. */
return iTrue;
}
if (!requestMedia_DocumentWidget_(d, linkId)) {
if (!requestMedia_DocumentWidget_(d, linkId, iTrue)) {
if (linkFlags & content_GmLinkFlag) {
/* Dismiss shown content on click. */
setData_Media(media_GmDocument(d->doc),
@@ -2622,9 +2641,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
"Open this link in the default browser?\n" uiTextAction_ColorEscape
"%s",
cstr_String(url)),
(const char *[]){ "Cancel", uiTextCaution_ColorEscape "Open Link" },
(const char *[]){
"cancel", format_CStr("!open default:1 url:%s", cstr_String(url)) },
(iMenuItem[]){
{ "Cancel", 0, 0, NULL },
{ uiTextCaution_ColorEscape "Open Link",
0, 0, format_CStr("!open default:1 url:%s", cstr_String(url)) } },
2);
}
}
@@ -2717,7 +2737,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
static void drawMark_DrawContext_(void *context, const iGmRun *run) {
iDrawContext *d = context;
fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark);
fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark);
}
@@ -2816,8 +2836,8 @@ static void drawBannerRun_DrawContext_(iDrawContext *d, const iGmRun *run, iInt2
static void drawRun_DrawContext_(void *context, const iGmRun *run) {
iDrawContext *d = context;
const iInt2 origin = d->viewPos;
SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->imageId);
SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->mediaId);
if (tex) {
const iRect dst = moved_Rect(run->visBounds, origin);
fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */
@@ -2826,8 +2846,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
}
return;
}
/* Audio player UI is drawn afterwards as a dynamic overlay. */
/* Media UIs are drawn afterwards as a dynamic overlay. */
return;
}
enum iColorId fg = run->color;
@@ -2847,12 +2867,10 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
}
if (run->flags & siteBanner_GmRunFlag) {
/* Banner background. */
fillRect_Paint(
&d->paint,
initCorners_Rect(topLeft_Rect(d->widgetBounds),
init_I2(right_Rect(bounds_Widget(constAs_Widget(d->widget))),
visPos.y + height_Rect(run->visBounds))),
tmBannerBackground_ColorId);
iRect bannerBack = initCorners_Rect(topLeft_Rect(d->widgetBounds),
init_I2(right_Rect(bounds_Widget(constAs_Widget(d->widget))),
visPos.y + height_Rect(run->visBounds)));
fillRect_Paint(&d->paint, bannerBack, tmBannerBackground_ColorId);
drawBannerRun_DrawContext_(d, run, visPos);
}
else {
@@ -2893,18 +2911,26 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
init_String(&text);
iMediaId imageId = linkImage_GmDocument(doc, run->linkId);
iMediaId audioId = !imageId ? linkAudio_GmDocument(doc, run->linkId) : 0;
iAssert(imageId || audioId);
iMediaId downloadId = !imageId && !audioId ?
findLinkDownload_Media(constMedia_GmDocument(doc), run->linkId) : 0;
iAssert(imageId || audioId || downloadId);
if (imageId) {
iAssert(!isEmpty_Rect(run->bounds));
iGmImageInfo info;
iGmMediaInfo info;
imageInfo_Media(constMedia_GmDocument(doc), imageId, &info);
const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), imageId);
format_String(&text, "%s \u2014 %d x %d \u2014 %.1fMB",
info.mime, info.size.x, info.size.y, info.numBytes / 1.0e6f);
info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f);
}
else if (audioId) {
iGmAudioInfo info;
iGmMediaInfo info;
audioInfo_Media(constMedia_GmDocument(doc), audioId, &info);
format_String(&text, "%s", info.mime);
format_String(&text, "%s", info.type);
}
else if (downloadId) {
iGmMediaInfo info;
downloadInfo_Media(constMedia_GmDocument(doc), downloadId, &info);
format_String(&text, "%s", info.type);
}
if (findMediaRequest_DocumentWidget_(d->widget, run->linkId)) {
appendFormat_String(
@@ -3032,11 +3058,11 @@ static void updateSideIconBuf_DocumentWidget_(iDocumentWidget *d) {
if (!banner) {
return;
}
/* Determine the required size. */
iInt2 bufSize = init1_I2(minBannerSize);
if (isHeadingVisible) {
@@ -3113,74 +3139,24 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
iMax(0, scrollMax_DocumentWidget_(d) - value_Anim(&d->scrollY)))),
tmQuoteIcon_ColorId);
}
-#if 0
/* TODO: This is very slow to draw; should be buffered appropriately. */
const int innerWidth = outlineWidth_DocumentWidget_(d);
const int outWidth = innerWidth + 2 * outlinePadding_DocumentWidget_ * gap_UI;
const int topMargin = 0;
const int bottomMargin = 3 * gap_UI;
const int scrollMax = scrollMax_DocumentWidget_(d);
const int outHeight = outlineHeight_DocumentWidget_(d);
const int oversize = outHeight - height_Rect(bounds) + topMargin + bottomMargin;
const int scroll = (oversize > 0 && scrollMax > 0
? oversize * value_Anim(&d->scrollY) / scrollMax_DocumentWidget_(d)
: 0);
iInt2 pos =
add_I2(topRight_Rect(bounds), init_I2(-outWidth - width_Widget(d->scroll), topMargin));
/* Center short outlines vertically. */
if (oversize < 0) {
pos.y -= oversize / 2;
}
pos.y -= scroll;
setOpacity_Text(outlineOpacity);
SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
p.alpha = outlineOpacity * 255;
iRect outlineFrame = {
addY_I2(pos, -outlinePadding_DocumentWidget_ * gap_UI / 2),
init_I2(outWidth, outHeight + outlinePadding_DocumentWidget_ * gap_UI * 1.5f)
};
fillRect_Paint(&p, outlineFrame, tmBannerBackground_ColorId);
drawSideRect_(&p, outlineFrame);
iBool wasAbove = iTrue;
iConstForEach(Array, i, &d->outline) {
const iOutlineItem *item = i.value;
iInt2 visPos = addX_I2(add_I2(pos, item->rect.pos), outlinePadding_DocumentWidget_ * gap_UI);
const iBool isVisible = d->lastVisibleRun && d->lastVisibleRun->text.start >= item->text.start;
const int fg = index_ArrayConstIterator(&i) == 0 || isVisible ? tmOutlineHeadingAbove_ColorId
: tmOutlineHeadingBelow_ColorId;
if (fg == tmOutlineHeadingBelow_ColorId) {
if (wasAbove) {
drawHLine_Paint(&p,
init_I2(left_Rect(outlineFrame), visPos.y - 1),
width_Rect(outlineFrame),
tmOutlineHeadingBelow_ColorId);
wasAbove = iFalse;
}
}
drawWrapRange_Text(
item->font, visPos, innerWidth - left_Rect(item->rect), fg, item->text);
if (left_Rect(item->rect) > 0) {
drawRange_Text(item->font, addX_I2(visPos, -2.75f * gap_UI), fg, range_CStr("\u2022"));
}
}
setOpacity_Text(1.0f);
SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
-#endif
unsetClip_Paint(&p);
}
-static void drawPlayers_DocumentWidget_(const iDocumentWidget *d, iPaint *p) {
+static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) {
const iGmRun * run = i.ptr;
const iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->audioId);
const iRect rect = playerRect_DocumentWidget_(d, run);
iPlayerUI ui;
init_PlayerUI(&ui, plr, rect);
draw_PlayerUI(&ui, p);
if (run->mediaType == audio_GmRunMediaType) {
iPlayerUI ui;
init_PlayerUI(&ui,
audioPlayer_Media(media_GmDocument(d->doc), run->mediaId),
runRect_DocumentWidget_(d, run));
draw_PlayerUI(&ui, p);
}
else if (run->mediaType == download_GmRunMediaType) {
iDownloadUI ui;
init_DownloadUI(&ui, d, run->mediaId, runRect_DocumentWidget_(d, run));
draw_DownloadUI(&ui, p);
}
}
}
@@ -3274,7 +3250,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
render_GmDocument(d->doc, vis, drawMark_DrawContext_, &ctx);
SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
}
unsetClip_Paint(&ctx.paint);
/* Fill the top and bottom, in case the document is short. */
if (yTop > top_Rect(bounds)) {
@@ -3299,6 +3275,9 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
fillRect_Paint(&ctx.paint, linkRect, tmBackground_ColorId);
drawRange_Text(font, addX_I2(topLeft_Rect(linkRect), gap_UI), tmParagraph_ColorId, linkUrl);
}
drawHLine_Paint(&ctx.paint, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId);
draw_Widget(w);
}
@@ -3320,6 +3299,10 @@ const iBlock *sourceContent_DocumentWidget(const iDocumentWidget *d) {
return &d->sourceContent;
}
+int documentWidth_DocumentWidget(const iDocumentWidget *d) {
+}
const iString *feedTitle_DocumentWidget(const iDocumentWidget *d) {
if (!isEmpty_String(title_GmDocument(d->doc))) {
return title_GmDocument(d->doc);
@@ -3394,10 +3377,9 @@ iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) {
}
void updateSize_DocumentWidget(iDocumentWidget *d) {
resetWideRuns_DocumentWidget_(d);
updateSideIconBuf_DocumentWidget_(d);
updateVisible_DocumentWidget_(d);
invalidate_DocumentWidget_(d);
}
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h
index 91337a24..f922e7ea 100644
--- a/src/ui/documentwidget.h
+++ b/src/ui/documentwidget.h
@@ -43,6 +43,7 @@ const iBlock * sourceContent_DocumentWidget (const iDocumentWidget *);
const iGmDocument * document_DocumentWidget (const iDocumentWidget *);
const iString * bookmarkTitle_DocumentWidget (const iDocumentWidget *);
const iString * feedTitle_DocumentWidget (const iDocumentWidget *);
+int documentWidth_DocumentWidget (const iDocumentWidget *);
void setUrl_DocumentWidget (iDocumentWidget *, const iString *url);
void setUrlFromCache_DocumentWidget (iDocumentWidget *, const iString *url, iBool isFromCache);
diff --git a/src/ui/indicatorwidget.c b/src/ui/indicatorwidget.c
index 96556912..4a829ae3 100644
--- a/src/ui/indicatorwidget.c
+++ b/src/ui/indicatorwidget.c
@@ -72,6 +72,7 @@ void init_IndicatorWidget(iIndicatorWidget *d) {
iWidget *w = &d->widget;
init_Widget(w);
init_Anim(&d->pos, 0);
}
static void startTimer_IndicatorWidget_(iIndicatorWidget *d) {
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 1674040b..600a4462 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -31,7 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include <SDL_clipboard.h>
#include <SDL_timer.h>
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
#endif
@@ -39,7 +39,7 @@ static const int refreshInterval_InputWidget_ = 256;
static const size_t maxUndo_InputWidget_ = 64;
static void enableEditorKeysInMenus_(iBool enable) {
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable);
enableMenuItemsByKey_MacOS(SDLK_RIGHT, KMOD_PRIMARY, enable);
#else
@@ -81,6 +81,8 @@ struct Impl_InputWidget {
iArray text; /* iChar[] */
iArray oldText; /* iChar[] */
iString hint;
size_t cursor;
size_t lastCursor;
iRanges mark;
@@ -108,12 +110,14 @@ static void showCursor_InputWidget_(iInputWidget *d) {
void init_InputWidget(iInputWidget *d, size_t maxLen) {
iWidget *w = &d->widget;
init_Widget(w);
init_Array(&d->text, sizeof(iChar));
init_Array(&d->oldText, sizeof(iChar));
init_String(&d->hint);
init_Array(&d->undoStack, sizeof(iInputUndo));
d->font = uiInput_FontId | alwaysVariableFlag_FontId;
d->cursor = 0;
d->lastCursor = 0;
d->inFlags = eatEscape_InputWidgetFlag;
@@ -121,6 +125,9 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
setMaxLen_InputWidget(d, maxLen);
/* Caller must arrange the width, but the height is fixed. */
w->rect.size.y = lineHeight_Text(default_FontId) + 2 * gap_UI;
+#if defined (iPlatformAppleMobile)
+#endif
setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue);
init_Click(&d->click, d, SDL_BUTTON_LEFT);
d->timer = 0;
@@ -130,6 +137,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
void deinit_InputWidget(iInputWidget *d) {
if (isSelected_Widget(d)) {
SDL_StopTextInput();
enableEditorKeysInMenus_(iTrue);
}
delete_TextBuf(d->buffered);
@@ -192,6 +200,16 @@ void setHint_InputWidget(iInputWidget *d, const char *hintText) {
setCStr_String(&d->hint, hintText);
}
+void setContentPadding_InputWidget(iInputWidget *d, int left, int right) {
d->leftPadding = left;
d->rightPadding = right;
+}
static const iChar sensitiveChar_ = 0x25cf; /* black circle */
static iString *visText_InputWidget_(const iInputWidget *d) {
@@ -342,10 +360,13 @@ static void insertChar_InputWidget_(iInputWidget *d, iChar chr) {
resize_Array(&d->text, d->cursor + 1);
}
set_Array(&d->text, d->cursor++, &chr);
if (d->maxLen && d->cursor == d->maxLen) {
if (d->maxLen > 1 && d->cursor == d->maxLen) {
iWidget *nextFocus = findFocusable_Widget(w, forward_WidgetFocusDir);
setFocus_Widget(nextFocus == w ? NULL : nextFocus);
}
else if (d->maxLen == 1) {
d->cursor = 0;
}
}
showCursor_InputWidget_(d);
refresh_Widget(as_Widget(d));
@@ -481,7 +502,9 @@ iLocalDef iInt2 padding_(void) {
static iInt2 textOrigin_InputWidget_(const iInputWidget *d, const char *visText) {
const iWidget *w = constAs_Widget(d);
addX_I2(padding_(), d->leftPadding),
neg_I2(addX_I2(padding_(), d->rightPadding)));
const iInt2 emSize = advance_Text(d->font, "M");
const int textWidth = advance_Text(d->font, visText).x;
const int cursorX = advanceN_Text(d->font, visText, d->cursor).x;
@@ -540,6 +563,27 @@ static iBool copy_InputWidget_(iInputWidget *d, iBool doCut) {
return iFalse;
}
+static void paste_InputWidget_(iInputWidget *d) {
pushUndo_InputWidget_(d);
deleteMarked_InputWidget_(d);
char * text = SDL_GetClipboardText();
iString *paste = collect_String(newCStr_String(text));
/* Url decoding. */
if (d->inFlags & isUrl_InputWidgetFlag) {
if (prefs_App()->decodeUserVisibleURLs) {
paste = collect_String(urlDecode_String(paste));
}
else {
urlEncodePath_String(paste);
}
}
SDL_free(text);
iConstForEach(String, i, paste) { insertChar_InputWidget_(d, i.value); }
contentsWereChanged_InputWidget_(d);
+}
static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
iWidget *w = as_Widget(d);
if (isCommand_Widget(w, ev, "focus.gained")) {
@@ -561,7 +605,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
return iTrue;
}
if (ev->type == SDL_MOUSEMOTION && isHover_Widget(d)) {
setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_IBEAM);
const iInt2 local = localCoord_Widget(w, init_I2(ev->motion.x, ev->motion.y));
setCursor_Window(get_Window(),
local.x >= 2 * gap_UI + d->leftPadding && local.x < width_Widget(w) - d->rightPadding
? SDL_SYSTEM_CURSOR_IBEAM
: SDL_SYSTEM_CURSOR_ARROW);
}
switch (processEvent_Click(&d->click, ev)) {
case none_ClickResult:
@@ -606,26 +654,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
copy_InputWidget_(d, key == 'x');
return iTrue;
case 'v':
if (SDL_HasClipboardText()) {
pushUndo_InputWidget_(d);
deleteMarked_InputWidget_(d);
char *text = SDL_GetClipboardText();
iString *paste = collect_String(newCStr_String(text));
/* Url decoding. */
if (d->inFlags & isUrl_InputWidgetFlag) {
if (prefs_App()->decodeUserVisibleURLs) {
paste = collect_String(urlDecode_String(paste));
}
else {
urlEncodePath_String(paste);
}
}
SDL_free(text);
iConstForEach(String, i, paste) {
insertChar_InputWidget_(d, i.value);
}
contentsWereChanged_InputWidget_(d);
}
paste_InputWidget_(d);
return iTrue;
case 'z':
if (popUndo_InputWidget_(d)) {
@@ -637,6 +666,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
}
d->lastCursor = d->cursor;
switch (key) {
case SDLK_INSERT:
if (mods == KMOD_SHIFT) {
paste_InputWidget_(d);
}
return iTrue;
case SDLK_RETURN:
case SDLK_KP_ENTER:
d->inFlags |= enterPressed_InputWidgetFlag;
@@ -664,6 +698,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
remove_Array(&d->text, --d->cursor);
contentsWereChanged_InputWidget_(d);
}
else if (d->cursor == 0 && d->maxLen == 1) {
pushUndo_InputWidget_(d);
clear_Array(&d->text);
contentsWereChanged_InputWidget_(d);
}
showCursor_InputWidget_(d);
refresh_Widget(w);
return iTrue;
@@ -804,7 +843,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
isFocused ? gap_UI / 4 : 1,
isFocused ? uiInputFrameFocused_ColorId
: isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId);
const iInt2 textOrigin = textOrigin_InputWidget_(d, cstr_String(text));
if (isFocused && !isEmpty_Range(&d->mark)) {
/* Draw the selected range. */
@@ -832,25 +871,37 @@ static void draw_InputWidget_(const iInputWidget *d) {
/* Cursor blinking. */
if (isFocused && d->cursorVis) {
iString cur;
if (d->cursor < size_Array(&d->text)) {
if (~d->inFlags & isSensitive_InputWidgetFlag) {
initUnicodeN_String(&cur, constAt_Array(&d->text, d->cursor), 1);
iInt2 curSize;
if (d->mode == overwrite_InputMode) {
/* Block cursor that overlaps a character. */
if (d->cursor < size_Array(&d->text)) {
if (~d->inFlags & isSensitive_InputWidgetFlag) {
initUnicodeN_String(&cur, constAt_Array(&d->text, d->cursor), 1);
}
else {
initUnicodeN_String(&cur, &sensitiveChar_, 1);
}
}
else {
initUnicodeN_String(&cur, &sensitiveChar_, 1);
initCStr_String(&cur, " ");
}
curSize = addX_I2(advance_Text(d->font, cstr_String(&cur)), iMin(2, gap_UI / 4));
}
else {
initCStr_String(&cur, " ");
/* Bar cursor. */
curSize = init_I2(gap_UI / 2, lineHeight_Text(d->font));
}
/* The `gap_UI` offsets below are a hack. They are used because for some reason the
cursor rect and the glyph inside don't quite position like during `run_Text_()`. */
const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(text), d->cursor);
const iInt2 curPos = addX_I2(textOrigin, prefixSize.x);
const iRect curRect = { curPos, addX_I2(advance_Text(d->font, cstr_String(&cur)), iMin(2, gap_UI / 4)) };
const iInt2 curPos = addX_I2(textOrigin, prefixSize.x +
(d->mode == insert_InputMode ? -curSize.x / 2 : 0));
const iRect curRect = { curPos, curSize };
fillRect_Paint(&p, curRect, uiInputCursor_ColorId);
draw_Text(d->font, addX_I2(curPos, iMin(1, gap_UI / 8)), uiInputCursorText_ColorId, "%s", cstr_String(&cur));
deinit_String(&cur);
if (d->mode == overwrite_InputMode) {
draw_Text(d->font, addX_I2(curPos, iMin(1, gap_UI / 8)), uiInputCursorText_ColorId, "%s", cstr_String(&cur));
deinit_String(&cur);
}
}
delete_String(text);
drawChildren_Widget(w);
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index 654433ea..3c7b2ffb 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -38,6 +38,7 @@ void setMaxLen_InputWidget (iInputWidget *, size_t maxLen);
void setText_InputWidget (iInputWidget *, const iString *text);
void setTextCStr_InputWidget (iInputWidget *, const char *cstr);
void setCursor_InputWidget (iInputWidget *, size_t pos);
+void setContentPadding_InputWidget (iInputWidget , int left, int right); / only affects the text entry */
void begin_InputWidget (iInputWidget *);
void end_InputWidget (iInputWidget *, iBool accept);
void selectAll_InputWidget (iInputWidget *);
diff --git a/src/ui/keys.c b/src/ui/keys.c
index 656a52ac..4aa42d36 100644
--- a/src/ui/keys.c
+++ b/src/ui/keys.c
@@ -81,9 +81,13 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] =
{ 46, { "Hover on link via home row keys", 'h', 0, "document.linkkeys arg:1 hover:1" }, 0 },
{ 47, { "Next set of home row key links", '.', 0, "document.linkkeys more:1" }, 0 },
{ 50, { "Add bookmark", 'd', KMOD_PRIMARY, "bookmark.add" }, 0 },
{ 70, { "Zoom in", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, 0 },
{ 71, { "Zoom out", SDLK_MINUS, KMOD_PRIMARY, "zoom.delta arg:-10" }, 0 },
{ 72, { "Reset zoom", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" }, 0 },
+#if !defined (iPlatformApple) /* Ctrl-Cmd-F on macOS */
+#endif
{ 76, { "New tab", newTab_KeyShortcut, "tabs.new" }, 0 },
{ 77, { "Close tab", closeTab_KeyShortcut, "tabs.close" }, 0 },
{ 80, { "Previous tab", prevTab_KeyShortcut, "tabs.prev" }, 0 },
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 57136509..8cba2d00 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -28,8 +28,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "util.h"
#include "keys.h"
-iLocalDef iInt2 padding_(int flags) {
+iLocalDef iInt2 padding_(int64_t flags) {
+#if defined (iPlatformAppleMobile)
+#else
return init_I2(flags & tight_WidgetFlag ? 3 * gap_UI / 2 : (3 * gap_UI), gap_UI);
+#endif
}
struct Impl_LabelWidget {
@@ -38,6 +42,7 @@ struct Impl_LabelWidget {
int font;
int key;
int kmods;
iString command;
iBool alignVisual; /* align according to visible bounds, not typography */
iClick click;
@@ -82,6 +87,19 @@ static iBool processEvent_LabelWidget_(iLabelWidget *d, const SDL_Event *ev) {
return iFalse;
}
if (!isEmpty_String(&d->command)) {
+#if 0 && defined (iPlatformAppleMobile)
/* Touch allows activating any button on release. */
switch (ev->type) {
case SDL_MOUSEBUTTONUP: {
const iInt2 mouse = init_I2(ev->button.x, ev->button.y);
if (contains_Widget(w, mouse)) {
trigger_LabelWidget_(d);
refresh_Widget(w);
}
break;
}
}
+#endif
switch (processEvent_Click(&d->click, ev)) {
case started_ClickResult:
setFlags_Widget(w, pressed_WidgetFlag, iTrue);
@@ -121,16 +139,17 @@ static void keyStr_LabelWidget_(const iLabelWidget *d, iString *str) {
static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int *frame1, int *frame2) {
const iWidget *w = constAs_Widget(d);
const iBool isButton = d->click.button != 0;
/* Default color state. */
*fg = uiText_ColorId;
*frame1 = isButton ? uiEmboss1_ColorId : d->widget.frameColor;
*frame2 = isButton ? uiEmboss2_ColorId : *frame1;
*fg = uiTextDisabled_ColorId;
}
if (isSel) {
@@ -177,6 +196,9 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
}
*fg = uiTextPressed_ColorId | permanent_ColorId;
}
*fg = d->forceFg;
}
static void draw_LabelWidget_(const iLabelWidget *d) {
@@ -205,7 +227,8 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
bottomRight_Rect(frameRect), bottomLeft_Rect(frameRect)
};
drawLines_Paint(&p, points + 2, 3, frame2);
drawLines_Paint(&p, points, 3, frame);
drawLines_Paint(
&p, points, !isHover_Widget(w) && flags & noTopFrame_WidgetFlag ? 2 : 3, frame);
}
}
setClip_Paint(&p, rect);
@@ -256,7 +279,7 @@ static void sizeChanged_LabelWidget_(iLabelWidget *d) {
void updateSize_LabelWidget(iLabelWidget *d) {
iWidget *w = as_Widget(d);
iInt2 size = add_I2(measure_Text(d->font, cstr_String(&d->label)), muli_I2(padding_(flags), 2));
if ((flags & drawKey_WidgetFlag) && d->key) {
iString str;
@@ -277,6 +300,7 @@ void updateSize_LabelWidget(iLabelWidget *d) {
void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) {
init_Widget(&d->widget);
d->font = uiLabel_FontId;
initCStr_String(&d->label, label);
if (cmd) {
initCStr_String(&d->command, cmd);
@@ -304,6 +328,13 @@ void setFont_LabelWidget(iLabelWidget *d, int fontId) {
updateSize_LabelWidget(d);
}
+void setTextColor_LabelWidget(iLabelWidget *d, int color) {
d->forceFg = color;
refresh_Widget(d);
+}
void setText_LabelWidget(iLabelWidget *d, const iString *text) {
updateText_LabelWidget(d, text);
updateSize_LabelWidget(d);
diff --git a/src/ui/labelwidget.h b/src/ui/labelwidget.h
index c91793e5..266c3b02 100644
--- a/src/ui/labelwidget.h
+++ b/src/ui/labelwidget.h
@@ -31,6 +31,7 @@ iDeclareObjectConstructionArgs(LabelWidget, const char *label, const char *comma
void setAlignVisually_LabelWidget(iLabelWidget *, iBool alignVisual);
void setFont_LabelWidget (iLabelWidget *, int fontId);
+void setTextColor_LabelWidget (iLabelWidget *, int color);
void setText_LabelWidget (iLabelWidget *, const iString text); / resizes widget */
void setTextCStr_LabelWidget (iLabelWidget *, const char *text);
void setCommand_LabelWidget (iLabelWidget *, const iString *command);
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c
index b27107df..d31ac2f5 100644
--- a/src/ui/listwidget.c
+++ b/src/ui/listwidget.c
@@ -26,6 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "util.h"
#include "command.h"
#include "visbuf.h"
+#include "app.h"
#include <the_Foundation/intset.h>
@@ -135,8 +136,13 @@ void updateVisible_ListWidget(iListWidget *d) {
}
void setItemHeight_ListWidget(iListWidget *d, int itemHeight) {
itemHeight += 1.5 * gap_UI;
d->itemHeight = itemHeight;
invalidate_ListWidget(d);
}
int scrollBarWidth_ListWidget(const iListWidget *d) {
@@ -292,12 +298,16 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
setHoverItem_ListWidget_(d, hover);
}
if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) {
int amount = -ev->wheel.y;
#if defined (iPlatformApple)
/* Momentum scrolling. */
scrollOffset_ListWidget(d, -ev->wheel.y * get_Window()->pixelRatio);
+# if defined (iPlatformAppleDesktop)
/* Momentum scrolling (in points). */
amount *= get_Window()->pixelRatio;
+# endif
#else
scrollOffset_ListWidget(d, -ev->wheel.y * 3 * d->itemHeight);
amount *= 3 * d->itemHeight;
#endif
scrollOffset_ListWidget(d, amount);
return iTrue;
}
switch (processEvent_Click(&d->click, ev)) {
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c
index 123ac6c4..7d6052e2 100644
--- a/src/ui/lookupwidget.c
+++ b/src/ui/lookupwidget.c
@@ -377,6 +377,9 @@ void init_LookupWidget(iLookupWidget *d) {
init_Widget(w);
setId_Widget(w, "lookup");
setFlags_Widget(w, focusable_WidgetFlag | resizeChildren_WidgetFlag, iTrue);
+#if defined (iPlatformAppleMobile)
+#endif
d->list = addChild_Widget(w, iClob(new_ListWidget()));
setItemHeight_ListWidget(d->list, lineHeight_Text(uiContent_FontId) * 1.25f);
d->cursor = iInvalidPos;
@@ -626,9 +629,6 @@ static iBool moveCursor_LookupWidget_(iLookupWidget *d, int delta) {
static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
iWidget *w = as_Widget(d);
const char *cmd = command_UserEvent(ev);
-// if (ev->type == SDL_MOUSEMOTION && contains_Widget(w, init_I2(ev->motion.x, ev->motion.y))) {
-// setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW);
-// }
if (isCommand_Widget(w, ev, "lookup.ready")) {
/* Take the results and present them in the list. */
presentResults_LookupWidget_(d);
@@ -637,9 +637,23 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
if (isResize_UserEvent(ev) || (equal_Command(cmd, "layout.changed") &&
equal_Rangecc(range_Command(cmd, "id"), "navbar"))) {
/* Position the lookup popup under the URL bar. */ {
const iWindow *window = get_Window();
const iInt2 rootSize = rootSize_Window(window);
const iRect navBarBounds = bounds_Widget(findWidget_App("navbar"));
setSize_Widget(w, init_I2(width_Widget(findWidget_App("url")),
get_Window()->root->rect.size.y / 2));
(rootSize.y - bottom_Rect(navBarBounds)) / 2));
setPos_Widget(w, bottomLeft_Rect(bounds_Widget(findWidget_App("url"))));
+#if defined (iPlatformAppleMobile)
/* TODO: Ask the system how tall the keyboard is. */ {
if (isLandscape_App()) {
w->rect.size.y = rootSize.y * 4 / 10;
}
else if (deviceType_App() == phone_AppDeviceType) {
w->rect.size.x = rootSize.x;
w->rect.pos.x = 0;
}
}
+#endif
arrange_Widget(w);
}
updateVisible_ListWidget(d->list);
diff --git a/src/ui/playerui.c b/src/ui/mediaui.c
similarity index 67%
rename from src/ui/playerui.c
rename to src/ui/mediaui.c
index 3e22a1d2..2fad0cec 100644
--- a/src/ui/playerui.c
+++ b/src/ui/mediaui.c
@@ -20,11 +20,16 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
-#include "playerui.h"
+#include "mediaui.h"
+#include "media.h"
+#include "documentwidget.h"
+#include "gmdocument.h"
#include "audio/player.h"
#include "paint.h"
#include "util.h"
+#include <the_Foundation/path.h>
static const char *volumeChar_(float volume) {
if (volume <= 0) {
return "\U0001f507";
@@ -72,8 +77,11 @@ static void drawPlayerButton_(iPaint *p, iRect rect, const char *label, int font
drawCentered_Text(font, frameRect, iTrue, fg, "%s", label);
}
+static const uint32_t sevenSegmentDigit_ = 0x1fbf0;
+static const char *sevenSegmentStr_ = "\U0001fbf0";
static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) { /* returns width */
const int hours = seconds / 3600;
const int mins = (seconds / 60) % 60;
const int secs = seconds % 60;
@@ -81,14 +89,14 @@ static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) {
iString num;
init_String(&num);
if (hours) {
appendChar_String(&num, sevenSegmentDigit + (hours % 10));
appendChar_String(&num, sevenSegmentDigit_ + (hours % 10));
appendChar_String(&num, ':');
}
appendChar_String(&num, ':');
iInt2 size = advanceRange_Text(font, range_String(&num));
if (align == right_Alignment) {
pos.x -= size.x;
@@ -134,10 +142,10 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) {
iRound(totalTime));
}
/* Scrubber. */
const int scrubMax = (s2 - s1) * streamProgress_Player(d->player);
drawHLine_Paint(p, init_I2(s1, yMid), part, bright);
drawHLine_Paint(p, init_I2(s1 + part, yMid), scrubMax - part, dim);
@@ -182,3 +190,84 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) {
dot);
}
}
+/----------------------------------------------------------------------------------------------/
+static void drawSevenSegmentBytes_(iInt2 pos, int color, size_t numBytes) {
appendChar_String(&digits, sevenSegmentDigit_);
int magnitude = 0;
while (numBytes) {
if (magnitude == 3) {
prependCStr_String(&digits, "\u2024");
}
else if (magnitude == 6) {
prependCStr_String(&digits, restore_ColorEscape "\u2024");
}
else if (magnitude == 9) {
prependCStr_String(&digits, "\u2024");
}
prependChar_String(&digits, sevenSegmentDigit_ + (numBytes % 10));
numBytes /= 10;
magnitude++;
}
if (magnitude > 6) {
prependCStr_String(&digits, uiTextStrong_ColorEscape);
}
+}
+void init_DownloadUI(iDownloadUI *d, const iDocumentWidget *doc, uint16_t mediaId, iRect bounds) {
+}
+iBool processEvent_DownloadUI(iDownloadUI *d, const SDL_Event *ev) {
+}
+void draw_DownloadUI(const iDownloadUI *d, iPaint *p) {
drawRange_Text(fonts[0], init_I2(x, y1), uiHeading_ColorId, baseName_Path(path));
init_I2(x, y2),
isFinished ? uiTextAction_ColorId : uiTextDim_ColorId,
isFinished ? "Download completed."
: "Download will be cancelled if this tab is closed.");
drawAlign_Text(uiLabel_FontId, pos, uiTextDim_ColorId, right_Alignment, "%.3f MB/s",
bytesPerSecond / 1.0e6);
drawAlign_Text(uiLabel_FontId, pos, uiTextDim_ColorId, right_Alignment, "\u2014 MB/s");
+}
diff --git a/src/ui/playerui.h b/src/ui/mediaui.h
similarity index 76%
rename from src/ui/playerui.h
rename to src/ui/mediaui.h
index a1f4ca9b..e79dedc0 100644
--- a/src/ui/playerui.h
+++ b/src/ui/mediaui.h
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#pragma once
#include <the_Foundation/rect.h>
+#include <SDL_events.h>
iDeclareType(Paint)
iDeclareType(Player)
@@ -42,3 +43,19 @@ struct Impl_PlayerUI {
void init_PlayerUI (iPlayerUI *, const iPlayer *player, iRect bounds);
void draw_PlayerUI (iPlayerUI *, iPaint *p);
+/----------------------------------------------------------------------------------------------/
+iDeclareType(DocumentWidget)
+iDeclareType(Media)
+iDeclareType(DownloadUI)
+struct Impl_DownloadUI {
+};
+void init_DownloadUI (iDownloadUI *, const iDocumentWidget *doc, uint16_t mediaId, iRect bounds);
+iBool processEvent_DownloadUI (iDownloadUI *, const SDL_Event *ev);
+void draw_DownloadUI (const iDownloadUI *, iPaint *p);
diff --git a/src/ui/scrollwidget.c b/src/ui/scrollwidget.c
index 649d0575..448103b5 100644
--- a/src/ui/scrollwidget.c
+++ b/src/ui/scrollwidget.c
@@ -41,7 +41,7 @@ void init_ScrollWidget(iScrollWidget *d) {
setId_Widget(w, "scroll");
setFlags_Widget(w,
fixedWidth_WidgetFlag | resizeToParentHeight_WidgetFlag |
moveToParentRightEdge_WidgetFlag,
moveToParentRightEdge_WidgetFlag | touchDrag_WidgetFlag,
iTrue);
w->rect.size.x = gap_UI * 3;
init_Click(&d->click, d, SDL_BUTTON_LEFT);
@@ -137,7 +137,7 @@ static void draw_ScrollWidget_(const iScrollWidget *d) {
if (bounds.size.x > 0) {
iPaint p;
init_Paint(&p);
drawVLine_Paint(&p, topLeft_Rect(bounds), height_Rect(bounds), uiSeparator_ColorId);
//drawVLine_Paint(&p, topLeft_Rect(bounds), height_Rect(bounds), uiSeparator_ColorId);
const iRect thumbRect = shrunk_Rect(
thumbRect_ScrollWidget_(d), init_I2(isPressed ? gap_UI : (gap_UI * 4 / 3), gap_UI / 2));
fillRect_Paint(&p, thumbRect, isPressed ? uiBackgroundPressed_ColorId : tmQuote_ColorId);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 679d8e6f..4e908f1e 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -96,6 +96,7 @@ struct Impl_SidebarWidget {
iLabelWidget * modeButtons[max_SidebarMode];
int maxButtonLabelWidth;
int width;
iWidget * resizer;
iWidget * menu;
iSidebarItem * contextItem; /* list item accessed in the context menu */
@@ -148,6 +149,10 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
if (isHidden_FeedEntry(entry)) {
continue; /* A hidden entry. */
}
/* Don't show entries in the far future. */
if (secondsSince_Time(&now, &entry->posted) < -24 * 60 * 60) {
continue;
}
/* Exclude entries that are too old for Visited to keep track of. */
if (secondsSince_Time(&now, &entry->discovered) > maxAge_Visited) {
break; /* the rest are even older */
@@ -191,10 +196,10 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
d->menu = makeMenu_Widget(
as_Widget(d),
(iMenuItem[]){ { "Open Entry in New Tab", 0, 0, "feed.entry.opentab" },
{ "Open Feed Page", 0, 0, "feed.entry.openfeed" },
{ "Mark as Read", 0, 0, "feed.entry.toggleread" },
{ "Add Bookmark...", 0, 0, "feed.entry.bookmark" },
{ "---", 0, 0, NULL },
{ "Open Feed Page", 0, 0, "feed.entry.openfeed" },
{ "Edit Feed...", 0, 0, "feed.entry.edit" },
{ uiTextCaution_ColorEscape "Unsubscribe...", 0, 0, "feed.entry.unsubscribe" },
{ "---", 0, 0, NULL },
@@ -211,6 +216,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
item->id = index_ArrayConstIterator(&i);
setRange_String(&item->label, head->text);
item->indent = head->level * 5 * gap_UI;
item->isBold = head->level == 0;
addItem_ListWidget(d->list, item);
iRelease(item);
}
@@ -322,7 +328,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) {
const iGmIdentity *ident = i.ptr;
iSidebarItem *item = new_SidebarItem();
item->id = index_PtrArrayConstIterator(&i);
item->id = (uint32_t) index_PtrArrayConstIterator(&i);
item->icon = ident->icon;
set_String(&item->label, collect_String(subject_TlsCertificate(ident->cert)));
iDate until;
@@ -406,6 +412,11 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
}
}
+static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) {
+}
iBool setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) {
if (d->mode == mode) {
return iFalse;
@@ -417,22 +428,21 @@ iBool setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) {
for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) {
setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode);
}
setBackgroundColor_Widget(as_Widget(d->list),
d->mode == documentOutline_SidebarMode ? tmBannerBackground_ColorId
: uiBackground_ColorId);
: uiBackgroundSidebar_ColorId);
/* Restore previous scroll position. */
setScrollPos_ListWidget(d->list, d->modeScroll[mode]);
return iTrue;
}
enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) {
}
int width_SidebarWidget(const iSidebarWidget *d) {
}
static const char *normalModeLabels_[max_SidebarMode] = {
@@ -451,6 +461,10 @@ static const char *tightModeLabels_[max_SidebarMode] = {
"\U0001f5b9",
};
+const char *icon_SidebarMode(enum iSidebarMode mode) {
+}
void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
iWidget *w = as_Widget(d);
init_Widget(w);
@@ -464,38 +478,66 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
iTrue);
iZap(d->modeScroll);
d->side = side;
+#if defined (iPlatformAppleMobile)
+#else
d->width = 60 * gap_UI;
+#endif
setFlags_Widget(w, fixedWidth_WidgetFlag, iTrue);
iWidget *vdiv = makeVDiv_Widget();
addChildFlags_Widget(w, vdiv, resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag);
d->modeButtons[i] = addChildFlags_Widget(
buttons,
iClob(new_LabelWidget(
tightModeLabels_[i],
format_CStr("%s.mode arg:%d", cstr_String(id_Widget(w)), i))),
frameless_WidgetFlag);
d->maxButtonLabelWidth =
iMaxi(d->maxButtonLabelWidth,
3 * gap_UI + measure_Text(uiLabel_FontId, normalModeLabels_[i]).x);
iWidget *buttons = new_Widget();
setId_Widget(buttons, "buttons");
for (int i = 0; i < max_SidebarMode; i++) {
if (deviceType_App() == phone_AppDeviceType && i == identities_SidebarMode) {
continue;
}
d->modeButtons[i] = addChildFlags_Widget(
buttons,
iClob(new_LabelWidget(
tightModeLabels_[i],
format_CStr("%s.mode arg:%d", cstr_String(id_Widget(w)), i))),
frameless_WidgetFlag | noBackground_WidgetFlag);
}
setButtonFont_SidebarWidget(d, isPhone ? uiLabelLarge_FontId : uiLabel_FontId);
addChildFlags_Widget(vdiv,
iClob(buttons),
arrangeHorizontal_WidgetFlag |
resizeWidthOfChildren_WidgetFlag |
arrangeHeight_WidgetFlag | resizeToParentWidth_WidgetFlag |
drawBackgroundToHorizontalSafeArea_WidgetFlag);
+// if (deviceType_App() == phone_AppDeviceType) {
setBackgroundColor_Widget(buttons, uiBackgroundSidebar_ColorId);
iLabelWidget *heading = new_LabelWidget("Identities", NULL);
setBackgroundColor_Widget(as_Widget(heading), uiBackgroundUnfocusedSelection_ColorId);
setTextColor_LabelWidget(heading, uiIcon_ColorId);
setFont_LabelWidget(addChild_Widget(vdiv, iClob(heading)),
uiLabelLarge_FontId);
}
iClob(buttons),
arrangeHorizontal_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
arrangeHeight_WidgetFlag | resizeToParentWidth_WidgetFlag);
iWidget *content = new_Widget();
setFlags_Widget(content, resizeChildren_WidgetFlag, iTrue);
d->list = new_ListWidget();
setPadding_Widget(as_Widget(d->list), 0, gap_UI, 0, gap_UI);
d->contextItem = NULL;
d->blank = new_Widget();
addChildFlags_Widget(content, iClob(d->blank), resizeChildren_WidgetFlag);
addChildFlags_Widget(vdiv, iClob(content), expand_WidgetFlag);
deviceType_App() == phone_AppDeviceType && d->side == right_SideBarSide ?
identities_SidebarMode : bookmarks_SidebarMode);
d->resizer =
addChildFlags_Widget(w,
iClob(new_Widget()),
@@ -503,6 +545,9 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
resizeToParentHeight_WidgetFlag |
(side == left_SideBarSide ? moveToParentRightEdge_WidgetFlag
: moveToParentLeftEdge_WidgetFlag));
setFlags_Widget(d->resizer, hidden_WidgetFlag | disabled_WidgetFlag, iTrue);
setId_Widget(d->resizer, side == left_SideBarSide ? "sidebar.grab" : "sidebar2.grab");
d->resizer->rect.size.x = gap_UI;
setBackgroundColor_Widget(d->resizer, none_ColorId);
@@ -514,6 +559,18 @@ void deinit_SidebarWidget(iSidebarWidget *d) {
deinit_String(&d->cmdPrefix);
}
+void setButtonFont_SidebarWidget(iSidebarWidget *d, int font) {
if (d->modeButtons[i]) {
setFont_LabelWidget(d->modeButtons[i], font);
d->maxButtonLabelWidth =
iMaxi(d->maxButtonLabelWidth,
3 * gap_UI + measure_Text(font, normalModeLabels_[i]).x);
}
+}
static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) {
if (d->mode == identities_SidebarMode) {
const iSidebarItem *hoverItem = constHoverItem_ListWidget(d->list);
@@ -581,9 +638,11 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, const iSidebarItem *it
}
static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) {
const iBool isTight =
(width_Rect(bounds_Widget(as_Widget(d->modeButtons[0]))) < d->maxButtonLabelWidth);
for (int i = 0; i < max_SidebarMode; i++) {
if (!d->modeButtons[i]) continue;
if (isTight && ~flags_Widget(as_Widget(d->modeButtons[i])) & tight_WidgetFlag) {
setFlags_Widget(as_Widget(d->modeButtons[i]), tight_WidgetFlag, iTrue);
updateTextCStr_LabelWidget(d->modeButtons[i], tightModeLabels_[i]);
@@ -596,17 +655,20 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) {
}
void setWidth_SidebarWidget(iSidebarWidget *d, int width) {
width_Widget(findWidget_App(d->side == left_SideBarSide ? "sidebar2" : "sidebar"));
/* Even less space if the other sidebar is visible, too. */
const int otherWidth =
width_Widget(findWidget_App(d->side == left_SideBarSide ? "sidebar2" : "sidebar"));
width = iClamp(width, 30 * gap_UI, rootSize_Window(get_Window()).x - 50 * gap_UI - otherWidth);
d->width = width;
if (isVisible_Widget(w)) {
w->rect.size.x = width;
}
checkModeButtonLayout_SidebarWidget_(d);
if (!isRefreshPending_App()) {
updateSize_DocumentWidget(document_App());
invalidate_ListWidget(d->list);
@@ -621,12 +683,24 @@ iBool handleBookmarkEditorCommands_SidebarWidget_(iWidget *editor, const char *c
const iString *title = text_InputWidget(findChild_Widget(editor, "bmed.title"));
const iString *url = text_InputWidget(findChild_Widget(editor, "bmed.url"));
const iString *tags = text_InputWidget(findChild_Widget(editor, "bmed.tags"));
const iString *icon = collect_String(trimmed_String(
text_InputWidget(findChild_Widget(editor, "bmed.icon"))));
const iSidebarItem *item = hoverItem_ListWidget(d->list);
iAssert(item); /* hover item cannot have been changed */
iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id);
set_String(&bm->title, title);
set_String(&bm->url, url);
set_String(&bm->tags, tags);
if (isEmpty_String(icon)) {
removeTag_Bookmark(bm, "usericon");
bm->icon = 0;
}
else {
if (!hasTag_Bookmark(bm, "usericon")) {
addTag_Bookmark(bm, "usericon");
}
bm->icon = first_String(icon);
}
postCommand_App("bookmarks.changed");
}
setFlags_Widget(as_Widget(d), disabled_WidgetFlag, iFalse);
@@ -650,6 +724,9 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
postCommandf_App("%s.toggle", cstr_String(id_Widget(w)));
}
scrollOffset_ListWidget(d->list, 0);
if (wasChanged) {
postCommandf_App("%s.mode.changed arg:%d", cstr_String(id_Widget(w)), d->mode);
}
return iTrue;
}
else if (equal_Command(cmd, "toggle")) {
@@ -771,6 +848,10 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
setText_InputWidget(findChild_Widget(dlg, "bmed.title"), &bm->title);
setText_InputWidget(findChild_Widget(dlg, "bmed.url"), &bm->url);
setText_InputWidget(findChild_Widget(dlg, "bmed.tags"), &bm->tags);
if (hasTag_Bookmark(bm, "usericon")) {
setText_InputWidget(findChild_Widget(dlg, "bmed.icon"),
collect_String(newUnicodeN_String(&bm->icon, 1)));
}
setCommandHandler_Widget(dlg, handleBookmarkEditorCommands_SidebarWidget_);
setFocus_Widget(findChild_Widget(dlg, "bmed.title"));
}
@@ -878,11 +959,12 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
uiTextCaution_ColorEscape "UNSUBSCRIBE",
format_CStr("Really unsubscribe from feed\n\"%s\"?",
cstr_String(&feedBookmark->title)),
(const char *[]){ "Cancel",
uiTextCaution_ColorEscape "Unsubscribe" },
(const char *[]){
"cancel",
format_CStr("!feed.entry.unsubscribe arg:1 ptr:%p", d) },
(iMenuItem[]){
{ "Cancel", 0, 0, NULL },
{ uiTextCaution_ColorEscape "Unsubscribe",
0,
0,
format_CStr("!feed.entry.unsubscribe arg:1 ptr:%p", d) } },
2);
}
return iTrue;
@@ -967,9 +1049,9 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
"%s\n" uiText_ColorEscape
"including its certificate and private key files?",
cstr_String(&item->label)),
(const char *[]){ "Cancel",
uiTextCaution_ColorEscape "Delete Identity and Files" },
(const char *[]){ "cancel", format_CStr("!ident.delete confirm:0 ptr:%p", d) },
(iMenuItem[]){ { "Cancel", 0, 0, NULL },
{ uiTextCaution_ColorEscape "Delete Identity and Files",
0, 0, format_CStr("!ident.delete confirm:0 ptr:%p", d) } },
2);
return iTrue;
}
@@ -1004,12 +1086,12 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
}
else if (equal_Command(cmd, "history.clear")) {
if (argLabel_Command(cmd, "confirm")) {
makeQuestion_Widget(
uiTextCaution_ColorEscape "CLEAR HISTORY",
"Do you really want to erase the history of all visited pages?",
(const char *[]){ "Cancel", uiTextCaution_ColorEscape "Clear History" },
(const char *[]){ "cancel", "history.clear confirm:0" },
2);
makeQuestion_Widget(uiTextCaution_ColorEscape "CLEAR HISTORY",
"Do you really want to erase the history of all visited pages?",
(iMenuItem[]){ { "Cancel", 0, 0, NULL },
{ uiTextCaution_ColorEscape "Clear History",
0, 0, "history.clear confirm:0" } },
2);
}
else {
clear_Visited(visited_App());
@@ -1166,8 +1248,8 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
const int itemHeight = height_Rect(itemRect);
const int iconColor = isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId)
: uiIcon_ColorId;
if (isHover) {
bg = isPressing ? uiBackgroundPressed_ColorId
: uiBackgroundFramelessHover_ColorId;
@@ -1197,6 +1279,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
else if (sidebar->mode == feeds_SidebarMode) {
const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId)
: uiText_ColorId;
const int iconPad = 12 * gap_UI;
if (d->listItem.isSeparator) {
if (d != constItem_ListWidget(list, 0)) {
drawHLine_Paint(p,
@@ -1205,19 +1288,18 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
uiSeparator_ColorId);
}
drawRange_Text(
uiLabelLarge_FontId,
uiLabelLargeBold_FontId,
add_I2(pos,
init_I2(3 * gap_UI,
itemHeight - lineHeight_Text(uiLabelLarge_FontId) - 1 * gap_UI)),
itemHeight - lineHeight_Text(uiLabelLargeBold_FontId) - 1 * gap_UI)),
uiIcon_ColorId,
range_String(&d->meta));
}
else {
const iBool isUnread = (d->indent != 0);
const int titleFont = isUnread ? uiContentBold_FontId : uiContent_FontId;
const int titleFont = sidebar->itemFonts[isUnread ? 1 : 0];
const int h1 = lineHeight_Text(uiLabel_FontId);
const int h2 = lineHeight_Text(titleFont);
const int iconPad = 9 * gap_UI;
iRect iconArea = { addY_I2(pos, 0), init_I2(iconPad, itemHeight) };
if (isUnread) {
fillRect_Paint(
@@ -1229,12 +1311,14 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
/* TODO: Use the primary hue from the theme of this site. */
iString str;
initUnicodeN_String(&str, &d->icon, 1);
drawCentered_Text(uiContent_FontId,
drawCentered_Text(uiLabelLarge_FontId,
adjusted_Rect(iconArea, init_I2(gap_UI, 0), zero_I2()),
iTrue,
isHover && isPressing
? iconColor
: (isUnread ? uiTextCaution_ColorId : iconColor),
: isUnread ? uiTextCaution_ColorId
: d->listItem.isSelected ? iconColor
: uiText_ColorId,
"%s",
cstr_String(&str));
deinit_String(&str);
@@ -1278,7 +1362,8 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
iString str;
init_String(&str);
appendChar_String(&str, d->icon ? d->icon : 0x1f588);
const iRect iconArea = { addX_I2(pos, gap_UI), init_I2(7 * gap_UI, itemHeight) };
const iRect iconArea = { addX_I2(pos, gap_UI),
init_I2(1.75f * lineHeight_Text(font), itemHeight) };
drawCentered_Text(font,
iconArea,
iTrue,
@@ -1315,9 +1400,9 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
width_Rect(itemRect) - scrollBarWidth,
uiSeparator_ColorId);
drawRange_Text(
uiLabelLarge_FontId,
uiLabelLargeBold_FontId,
add_I2(drawPos,
init_I2(3 * gap_UI, (itemHeight - lineHeight_Text(uiLabelLarge_FontId)) / 2)),
init_I2(3 * gap_UI, (itemHeight - lineHeight_Text(uiLabelLargeBold_FontId)) / 2)),
uiIcon_ColorId,
range_String(&d->meta));
}
diff --git a/src/ui/sidebarwidget.h b/src/ui/sidebarwidget.h
index fa74e049..d7029b0f 100644
--- a/src/ui/sidebarwidget.h
+++ b/src/ui/sidebarwidget.h
@@ -33,6 +33,8 @@ enum iSidebarMode {
max_SidebarMode
};
+const char * icon_SidebarMode (enum iSidebarMode mode);
enum iSidebarSide {
left_SideBarSide,
right_SideBarSide,
@@ -41,8 +43,9 @@ enum iSidebarSide {
iDeclareWidgetClass(SidebarWidget)
iDeclareObjectConstructionArgs(SidebarWidget, enum iSidebarSide side)
-iBool setMode_SidebarWidget (iSidebarWidget *, enum iSidebarMode mode);
+iBool setMode_SidebarWidget (iSidebarWidget *, enum iSidebarMode mode);
+void setButtonFont_SidebarWidget (iSidebarWidget *, int font);
-enum iSidebarMode mode_SidebarWidget (const iSidebarWidget *);
-int width_SidebarWidget (const iSidebarWidget *);
-void setWidth_SidebarWidget (iSidebarWidget *, int width);
+enum iSidebarMode mode_SidebarWidget (const iSidebarWidget *);
+int width_SidebarWidget (const iSidebarWidget *);
+void setWidth_SidebarWidget (iSidebarWidget *, int width);
diff --git a/src/ui/text.c b/src/ui/text.c
index 93ea323e..ee838ce7 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -234,6 +234,18 @@ static void initFonts_Text_(iText *d) {
italicFont = &fontLiterataLightItalicopsz10_Embedded;
lightFont = &fontLiterataExtraLightopsz18_Embedded;
}
regularFont = &fontSourceSansProRegular_Embedded;
italicFont = &fontFiraSansItalic_Embedded;
lightFont = &fontFiraSansLight_Embedded;
lightScaling = 0.85f;
regularFont = &fontIosevkaTermExtended_Embedded;
italicFont = &fontIosevkaTermExtended_Embedded;
lightFont = &fontIosevkaTermExtended_Embedded;
scaling = lightScaling = 0.866f;
if (d->headingFont == firaSans_TextFont) {
h12Font = &fontFiraSansBold_Embedded;
h3Font = &fontFiraSansRegular_Embedded;
@@ -248,18 +260,34 @@ static void initFonts_Text_(iText *d) {
h12Font = &fontLiterataBoldopsz36_Embedded;
h3Font = &fontLiterataRegularopsz14_Embedded;
}
h12Font = &fontSourceSansProBold_Embedded;
h3Font = &fontSourceSansProRegular_Embedded;
h12Font = &fontIosevkaTermExtended_Embedded;
h3Font = &fontIosevkaTermExtended_Embedded;
+#if defined (iPlatformAppleMobile)
+#else
+#endif
const struct {
const iBlock *ttf;
int size;
float scaling;
int symbolsFont;
} fontData[max_FontId] = {
{ &fontSourceSansProRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId },
{ &fontSourceSansProBold_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId },
{ &fontSourceSansProRegular_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId },
{ &fontSourceSansProBold_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId },
{ &fontSourceSansProRegular_Embedded, fontSize_UI * 1.666f, 1.0f, defaultLargeSymbols_FontId },
{ &fontIosevkaTermExtended_Embedded, fontSize_UI * 0.866f, 1.0f, defaultSymbols_FontId },
{ &fontSourceSansProRegular_Embedded, uiSize, 1.0f, defaultSymbols_FontId },
{ &fontSourceSansProBold_Embedded, uiSize, 1.0f, defaultSymbols_FontId },
{ &fontSourceSansProRegular_Embedded, uiSize * 1.125f, 1.0f, defaultMediumSymbols_FontId },
{ &fontSourceSansProBold_Embedded, uiSize * 1.125f, 1.0f, defaultMediumSymbols_FontId },
{ &fontSourceSansProRegular_Embedded, uiSize * 1.333f, 1.0f, defaultBigSymbols_FontId },
{ &fontSourceSansProBold_Embedded, uiSize * 1.333f, 1.0f, defaultBigSymbols_FontId },
{ &fontSourceSansProRegular_Embedded, uiSize * 1.666f, 1.0f, defaultLargeSymbols_FontId },
{ &fontSourceSansProBold_Embedded, uiSize * 1.666f, 1.0f, defaultLargeSymbols_FontId },
{ &fontIosevkaTermExtended_Embedded, uiSize * 0.866f, 1.0f, defaultSymbols_FontId },
{ &fontSourceSansProRegular_Embedded, textSize, scaling, symbols_FontId },
/* content fonts */
{ regularFont, textSize, scaling, symbols_FontId },
@@ -274,9 +302,10 @@ static void initFonts_Text_(iText *d) {
/* monospace content fonts */
{ &fontIosevkaTermExtended_Embedded, textSize, 0.866f, symbols_FontId },
/* symbol fonts */
{ &fontSymbola_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId },
{ &fontSymbola_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId },
{ &fontSymbola_Embedded, fontSize_UI * 1.666f, 1.0f, defaultLargeSymbols_FontId },
{ &fontSymbola_Embedded, uiSize, 1.0f, defaultSymbols_FontId },
{ &fontSymbola_Embedded, uiSize * 1.125f, 1.0f, defaultMediumSymbols_FontId },
{ &fontSymbola_Embedded, uiSize * 1.333f, 1.0f, defaultBigSymbols_FontId },
{ &fontSymbola_Embedded, uiSize * 1.666f, 1.0f, defaultLargeSymbols_FontId },
{ &fontSymbola_Embedded, textSize, 1.0f, symbols_FontId },
{ &fontSymbola_Embedded, textSize * 1.200f, 1.0f, mediumSymbols_FontId },
{ &fontSymbola_Embedded, textSize * 1.333f, 1.0f, bigSymbols_FontId },
@@ -285,9 +314,10 @@ static void initFonts_Text_(iText *d) {
{ &fontSymbola_Embedded, monoSize, 1.0f, monospaceSymbols_FontId },
{ &fontSymbola_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId },
/* emoji fonts */
{ &fontNotoEmojiRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId },
{ &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId },
{ &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.666f, 1.0f, defaultLargeSymbols_FontId },
{ &fontNotoEmojiRegular_Embedded, uiSize, 1.0f, defaultSymbols_FontId },
{ &fontNotoEmojiRegular_Embedded, uiSize * 1.125f, 1.0f, defaultMediumSymbols_FontId },
{ &fontNotoEmojiRegular_Embedded, uiSize * 1.333f, 1.0f, defaultBigSymbols_FontId },
{ &fontNotoEmojiRegular_Embedded, uiSize * 1.666f, 1.0f, defaultLargeSymbols_FontId },
{ &fontNotoEmojiRegular_Embedded, textSize, 1.0f, symbols_FontId },
{ &fontNotoEmojiRegular_Embedded, textSize * 1.200f, 1.0f, mediumSymbols_FontId },
{ &fontNotoEmojiRegular_Embedded, textSize * 1.333f, 1.0f, bigSymbols_FontId },
@@ -296,7 +326,7 @@ static void initFonts_Text_(iText *d) {
{ &fontNotoEmojiRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId },
{ &fontNotoEmojiRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId },
/* japanese fonts */
{ &fontNotoSansJPRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId },
{ &fontNotoSansJPRegular_Embedded, uiSize, 1.0f, defaultSymbols_FontId },
{ &fontNotoSansJPRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId },
{ &fontNotoSansJPRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId },
{ &fontNotoSansJPRegular_Embedded, textSize, 1.0f, symbols_FontId },
@@ -305,7 +335,7 @@ static void initFonts_Text_(iText *d) {
{ &fontNotoSansJPRegular_Embedded, textSize * 1.666f, 1.0f, largeSymbols_FontId },
{ &fontNotoSansJPRegular_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId },
/* korean fonts */
{ &fontNanumGothicRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId },
{ &fontNanumGothicRegular_Embedded, uiSize, 1.0f, defaultSymbols_FontId },
{ &fontNanumGothicRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId },
{ &fontNanumGothicRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId },
{ &fontNanumGothicRegular_Embedded, textSize, 1.0f, symbols_FontId },
@@ -331,6 +361,7 @@ static void initFonts_Text_(iText *d) {
the other sizes. */
font_Text_(default_FontId)->japaneseFont = defaultJapanese_FontId;
font_Text_(defaultMedium_FontId)->japaneseFont = defaultJapanese_FontId;
font_Text_(defaultBig_FontId)->japaneseFont = defaultJapanese_FontId;
font_Text_(defaultLarge_FontId)->japaneseFont = defaultJapanese_FontId;
font_Text_(defaultMonospace_FontId)->japaneseFont = defaultJapanese_FontId;
font_Text_(monospaceSmall_FontId)->japaneseFont = monospaceSmallJapanese_FontId;
@@ -344,6 +375,7 @@ static void initFonts_Text_(iText *d) {
/* Korean script. */ {
font_Text_(default_FontId)->koreanFont = defaultKorean_FontId;
font_Text_(defaultMedium_FontId)->koreanFont = defaultKorean_FontId;
font_Text_(defaultBig_FontId)->koreanFont = defaultKorean_FontId;
font_Text_(defaultLarge_FontId)->koreanFont = defaultKorean_FontId;
font_Text_(defaultMonospace_FontId)->koreanFont = defaultKorean_FontId;
font_Text_(monospaceSmall_FontId)->koreanFont = monospaceSmallKorean_FontId;
@@ -386,6 +418,7 @@ static void initCache_Text_(iText *d) {
pushBack_Array(&d->cacheRows, &(iCacheRow){ .height = 0 });
}
d->cacheBottom = 0;
d->cache = SDL_CreateTexture(d->render,
SDL_PIXELFORMAT_RGBA4444,
SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
@@ -669,7 +702,7 @@ static iChar nextChar_(const char **chPos, const char *end) {
}
static enum iFontId fontId_Text_(const iFont *font) {
}
iLocalDef iBool isWrapBoundary_(iChar prevC, iChar c) {
@@ -706,6 +739,7 @@ struct Impl_RunArgs {
iInt2 pos;
int xposLimit; /* hard limit for wrapping */
int xposLayoutBound; /* visible bound for layout purposes; does not affect wrapping */
const char ** continueFrom_out;
int * runAdvance_out;
};
@@ -735,8 +769,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
for (const char *chPos = args->text.start; chPos != args->text.end; ) {
iAssert(chPos < args->text.end);
const char *currentPos = chPos;
if (*chPos == 0x1b) {
/* ANSI escape. */
if (*chPos == 0x1b) { /* ANSI escape. */
chPos++;
iRegExpMatch m;
init_RegExpMatch(&m);
@@ -761,17 +794,6 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
ch = nextChar_(&chPos, args->text.end);
}
}
-#if 0
iChar nextCh = 0; {
/* TODO: Since we're peeking ahead, should use this on the next loop iteration. */
const char *ncp = chPos;
nextCh = nextChar_(&ncp, text.end);
}
/* VS15: Peek ahead and treat as Emoji font. */
if (nextCh == emojiVariationSelector_Char) {
isEmoji = iTrue;
}
-#endif
if (isVariationSelector_Char(ch)) {
ch = nextChar_(&chPos, args->text.end); /* skip it */
}
@@ -822,9 +844,17 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
continue;
}
if (ch == '\r') {
const iChar esc = nextChar_(&chPos, args->text.end);
iChar esc = nextChar_(&chPos, args->text.end);
int colorNum = args->color;
if (esc != 0x24) { /* ASCII Cancel */
colorNum = esc - asciiBase_ColorEscape;
}
else if (esc == '\r') { /* Extended range. */
esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape;
colorNum = esc - asciiBase_ColorEscape;
}
if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {
const iColor clr = get_Color(esc - asciiBase_ColorEscape);
const iColor clr = get_Color(colorNum);
SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b);
}
prevCh = 0;
@@ -1024,9 +1054,10 @@ static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, i
&(iRunArgs){ .mode = draw_RunMode |
(color & permanent_ColorId ? permanentColorFlag_RunMode : 0) |
runFlagsFromId_(fontId),
.text = text,
.pos = pos,
.xposLayoutBound = xposBound });
.text = text,
.pos = pos,
.xposLayoutBound = xposBound,
.color = color });
}
static void draw_Text_(int fontId, iInt2 pos, int color, iRangecc text) {
@@ -1112,6 +1143,7 @@ void drawCentered_Text(int fontId, iRect rect, iBool alignVisual, int color, con
iRect textBounds = alignVisual ? visualBounds_Text(fontId, text)
: (iRect){ zero_I2(), advanceRange_Text(fontId, text) };
textBounds.pos = sub_I2(mid_Rect(rect), mid_Rect(textBounds));
draw_Text_(fontId, textBounds.pos, color, text);
deinit_Block(&chars);
}
@@ -1232,6 +1264,7 @@ iDefineTypeConstructionArgs(TextBuf, (int font, const char *text), font, text)
void init_TextBuf(iTextBuf *d, int font, const char *text) {
SDL_Renderer *render = text_.render;
d->size = advance_Text(font, text);
d->texture = SDL_CreateTexture(render,
SDL_PIXELFORMAT_RGBA4444,
SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
diff --git a/src/ui/text.h b/src/ui/text.h
index 217bdd13..f696b2e3 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -34,7 +34,10 @@ enum iFontId {
defaultBold_FontId,
defaultMedium_FontId,
defaultMediumBold_FontId,
defaultLarge_FontId,
defaultMonospace_FontId,
defaultContentSized_FontId,
/* content fonts */
@@ -52,6 +55,7 @@ enum iFontId {
/* symbol fonts */
defaultSymbols_FontId,
defaultMediumSymbols_FontId,
defaultLargeSymbols_FontId,
symbols_FontId,
mediumSymbols_FontId,
@@ -63,6 +67,7 @@ enum iFontId {
/* emoji fonts */
defaultEmoji_FontId,
defaultMediumEmoji_FontId,
defaultLargeEmoji_FontId,
emoji_FontId,
mediumEmoji_FontId,
@@ -92,7 +97,7 @@ enum iFontId {
max_FontId,
/* Meta: */
mask_FontId = 0xffff,
alwaysVariableFlag_FontId = 0x10000,
@@ -100,8 +105,9 @@ enum iFontId {
uiLabel_FontId = default_FontId,
uiLabelBold_FontId = defaultBold_FontId,
uiLabelLarge_FontId = defaultLarge_FontId,
uiShortcuts_FontId = default_FontId,
uiContent_FontId = defaultMedium_FontId,
uiContentBold_FontId = defaultMediumBold_FontId,
uiContentSymbols_FontId = defaultMediumSymbols_FontId,
@@ -142,6 +148,8 @@ enum iTextFont {
firaSans_TextFont,
literata_TextFont,
tinos_TextFont,
};
extern int gap_Text; /* affected by content font size */
diff --git a/src/ui/touch.c b/src/ui/touch.c
new file mode 100644
index 00000000..5bf1b87e
--- /dev/null
+++ b/src/ui/touch.c
@@ -0,0 +1,413 @@
+/* Copyright 2021 Jaakko Keränen jaakko.keranen@iki.fi
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+1. Redistributions of source code must retain the above copyright notice, this
+2. Redistributions in binary form must reproduce the above copyright notice,
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+#include "touch.h"
+#include "window.h"
+#include "app.h"
+#include <the_Foundation/array.h>
+#include <the_Foundation/math.h>
+#include <SDL_timer.h>
+iDeclareType(Touch)
+iDeclareType(TouchState)
+iDeclareType(Momentum)
+#define numHistory_Touch_ 3
+#define lastIndex_Touch_ (numHistory_Touch_ - 1)
+enum iTouchEdge {
+};
+struct Impl_Touch {
+};
+iLocalDef void pushPos_Touch_(iTouch *d, const iFloat3 pos, uint32_t time) {
+}
+struct Impl_Momentum {
+};
+struct Impl_TouchState {
+};
+static iTouchState *touchState_(void) {
d->touches = new_Array(sizeof(iTouch));
d->moms = new_Array(sizeof(iMomentum));
d->lastMomTime = SDL_GetTicks();
+}
+static iTouch *find_TouchState_(iTouchState *d, SDL_FingerID id) {
iTouch *touch = (iTouch *) i.value;
if (touch->id == id) {
return touch;
}
+}
+static const uint32_t longPressSpanMs_ = 425;
+static const int tapRadiusPt_ = 15;
+static iBool isStationary_Touch_(const iTouch *d) {
length_F3(sub_F3(d->pos[0], d->startPos)) < tapRadiusPt_ * get_Window()->pixelRatio;
+}
+static void dispatchMotion_Touch_(iFloat3 pos, int buttonState) {
.type = SDL_MOUSEMOTION,
.timestamp = SDL_GetTicks(),
.which = SDL_TOUCH_MOUSEID,
.state = buttonState,
.x = x_F3(pos),
.y = y_F3(pos)
+}
+static void dispatchClick_Touch_(const iTouch *d, int button) {
.type = SDL_MOUSEBUTTONDOWN,
.button = button,
.clicks = 1,
.state = SDL_PRESSED,
.timestamp = SDL_GetTicks(),
.which = SDL_TOUCH_MOUSEID,
.x = x_F3(tapPos),
.y = y_F3(tapPos)
+}
+static void clearWidgetMomentum_TouchState_(iTouchState *d, iWidget *widget) {
iMomentum *mom = m.value;
if (mom->affinity == widget) {
remove_ArrayIterator(&m);
}
+}
+static void update_TouchState_(void *ptr) {
iTouch *touch = i.value;
/* Holding a touch will reset previous momentum for this widget. */
if (isStationary_Touch_(touch)) {
if (nowTime - touch->startTime > 25) {
clearWidgetMomentum_TouchState_(d, touch->affinity);
}
if (!touch->isTapAndHold && nowTime - touch->startTime >= longPressSpanMs_ &&
touch->affinity) {
dispatchClick_Touch_(touch, SDL_BUTTON_RIGHT);
touch->isTapAndHold = iTrue;
touch->hasMoved = iFalse;
touch->startPos = touch->pos[0];
}
}
const float minSpeed = 15.0f;
const float momFriction = 0.985f; /* per step */
const float stepDurationMs = 1000.0f / 120.0f;
double momAvailMs = nowTime - d->lastMomTime;
int numSteps = (int) (momAvailMs / stepDurationMs);
d->lastMomTime += numSteps * stepDurationMs;
numSteps = iMin(numSteps, 10); /* don't spend too much time here */
+// printf("mom steps:%d\n", numSteps);
iWindow *window = get_Window();
iForEach(Array, m, d->moms) {
if (numSteps == 0) break;
iMomentum *mom = m.value;
for (int step = 0; step < numSteps; step++) {
mulvf_F3(&mom->velocity, momFriction);
addv_F3(&mom->accum, mulf_F3(mom->velocity, stepDurationMs / 1000.0f));
}
const iInt2 points = initF3_I2(mom->accum);
if (points.x || points.y) {
subv_F3(&mom->accum, initI2_F3(points));
dispatchEvent_Widget(mom->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){
.type = SDL_MOUSEWHEEL,
.timestamp = nowTime,
.which = 0, /* means "precise scrolling" in DocumentWidget */
.x = points.x,
.y = points.y
});
}
if (length_F3(mom->velocity) < minSpeed) {
remove_ArrayIterator(&m);
}
}
addTicker_App(update_TouchState_, ptr);
+}
+void widgetDestroyed_Touch(iWidget *widget) {
iTouch *touch = i.value;
if (touch->affinity == widget) {
remove_ArrayIterator(&i);
}
iMomentum *mom = m.value;
if (mom->affinity == widget) {
remove_ArrayIterator(&m);
}
+}
+static void dispatchButtonUp_Touch_(iFloat3 pos) {
.type = SDL_MOUSEBUTTONUP,
.timestamp = SDL_GetTicks(),
.clicks = 1,
.state = SDL_RELEASED,
.which = SDL_TOUCH_MOUSEID,
.button = SDL_BUTTON_LEFT,
.x = x_F3(pos),
.y = y_F3(pos)
+}
+iBool processEvent_Touch(const SDL_Event *ev) {
return iFalse;
/* Register the new touch. */
const float x = x_F3(pos);
enum iTouchEdge edge = none_TouchEdge;
const int edgeWidth = 30 * window->pixelRatio;
if (x < edgeWidth) {
edge = left_TouchEdge;
}
else if (x > rootSize.x - edgeWidth) {
edge = right_TouchEdge;
}
iWidget *aff = hitChild_Widget(window->root, init_I2(iRound(x), iRound(y_F3(pos))));
/* TODO: We must retain a reference to the affinity widget, or otherwise it might
be destroyed during the gesture. */
+// printf("aff:%p (%s)\n", aff, aff ? class_Widget(aff)->name : "-");
if (flags_Widget(aff) & touchDrag_WidgetFlag) {
dispatchEvent_Widget(window->root, (SDL_Event *) &(SDL_MouseButtonEvent){
.type = SDL_MOUSEBUTTONDOWN,
.timestamp = fing->timestamp,
.clicks = 1,
.state = SDL_PRESSED,
.which = SDL_TOUCH_MOUSEID,
.button = SDL_BUTTON_LEFT,
.x = x_F3(pos),
.y = y_F3(pos)
});
edge = none_TouchEdge;
}
pushBack_Array(d->touches, &(iTouch){
.id = fing->fingerId,
.affinity = aff,
.hasMoved = (flags_Widget(aff) & touchDrag_WidgetFlag) != 0,
.edge = edge,
.startTime = nowTime,
.startPos = pos,
.pos = pos
});
/* Some widgets rely on hover state. */
dispatchMotion_Touch_(pos, 0);
addTicker_App(update_TouchState_, d);
iTouch *touch = find_TouchState_(d, fing->fingerId);
if (touch && touch->affinity) {
if (touch->isTapAndHold) {
pushPos_Touch_(touch, pos, fing->timestamp);
if (!touch->hasMoved && !isStationary_Touch_(touch)) {
touch->hasMoved = iTrue;
}
dispatchMotion_Touch_(pos, 0);
return iTrue;
}
if (flags_Widget(touch->affinity) & touchDrag_WidgetFlag) {
dispatchMotion_Touch_(pos, SDL_BUTTON_LMASK);
return iTrue;
}
/* Update touch position. */
pushPos_Touch_(touch, pos, nowTime);
const iFloat3 amount = mul_F3(init_F3(fing->dx, fing->dy, 0),
init_F3(rootSize.x, rootSize.y, 0));
addv_F3(&touch->accum, amount);
iInt2 pixels = initF3_I2(touch->accum);
/* We're reporting scrolling as full points, so keep track of the precise distance. */
subv_F3(&touch->accum, initI2_F3(pixels));
if (!touch->hasMoved && !isStationary_Touch_(touch)) {
touch->hasMoved = iTrue;
}
/* Edge swipe aborted? */
if (touch->edge == left_TouchEdge && fing->dx < 0) {
touch->edge = none_TouchEdge;
}
if (touch->edge == right_TouchEdge && fing->dx > 0) {
touch->edge = none_TouchEdge;
}
if (touch->edge) {
pixels.y = 0;
}
+// printf("%p (%s) py: %i wy: %f acc: %f\n",
+// touch->affinity,
+// class_Widget(touch->affinity)->name,
+// pixels.y, y_F3(amount), y_F3(touch->accum));
if (pixels.x || pixels.y) {
dispatchMotion_Touch_(pos, 0);
dispatchEvent_Widget(touch->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){
.type = SDL_MOUSEWHEEL,
.timestamp = SDL_GetTicks(),
.which = 0, /* means "precise scrolling" in DocumentWidget */
.x = pixels.x,
.y = pixels.y,
});
dispatchMotion_Touch_(zero_F3(), 0);
/* TODO: Keep increasing movement if the direction is the same. */
clearWidgetMomentum_TouchState_(d, touch->affinity);
}
}
iTouch *touch = find_TouchState_(d, fing->fingerId);
iForEach(Array, i, d->touches) {
iTouch *touch = i.value;
if (touch->id != fing->fingerId) {
continue;
}
if (touch->isTapAndHold) {
if (!isStationary_Touch_(touch)) {
dispatchClick_Touch_(touch, SDL_BUTTON_LEFT);
}
remove_ArrayIterator(&i);
continue;
}
if (flags_Widget(touch->affinity) & touchDrag_WidgetFlag) {
dispatchButtonUp_Touch_(pos);
remove_ArrayIterator(&i);
continue;
}
/* Edge swipes do not generate momentum. */
const uint32_t duration = nowTime - touch->startTime;
const iFloat3 gestureVector = sub_F3(pos, touch->startPos);
iFloat3 velocity = zero_F3();
if (touch->edge && fabsf(2 * x_F3(gestureVector)) > fabsf(y_F3(gestureVector)) &&
!isStationary_Touch_(touch)) {
dispatchClick_Touch_(touch, touch->edge == left_TouchEdge ? SDL_BUTTON_X1
: SDL_BUTTON_X2);
}
else {
const uint32_t elapsed = fing->timestamp - touch->posTime[lastIndex_Touch_];
const float minVelocity = 400.0f;
if (elapsed < 75) {
velocity = divf_F3(sub_F3(pos, touch->pos[lastIndex_Touch_]),
(float) elapsed / 1000.0f);
if (fabsf(x_F3(velocity)) < minVelocity) {
setX_F3(&velocity, 0.0f);
}
if (fabsf(y_F3(velocity)) < minVelocity) {
setY_F3(&velocity, 0.0f);
}
}
+// printf("elap:%ums vel:%f\n", elapsed, length_F3(velocity));
pushPos_Touch_(touch, pos, nowTime);
/* If short and didn't move far, do a tap (left click). */
if (duration < longPressSpanMs_ && isStationary_Touch_(touch)) {
dispatchMotion_Touch_(pos, SDL_BUTTON_LMASK);
dispatchClick_Touch_(touch, SDL_BUTTON_LEFT);
}
else if (length_F3(velocity) > 0.0f) {
clearWidgetMomentum_TouchState_(d, touch->affinity);
iMomentum mom = {
.affinity = touch->affinity,
.releaseTime = nowTime,
.velocity = velocity
};
if (isEmpty_Array(d->moms)) {
d->lastMomTime = nowTime;
}
pushBack_Array(d->moms, &mom);
dispatchMotion_Touch_(touch->startPos, 0);
}
else {
dispatchButtonUp_Touch_(pos);
}
}
remove_ArrayIterator(&i);
}
+}
diff --git a/src/ui/touch.h b/src/ui/touch.h
new file mode 100644
index 00000000..39a059da
--- /dev/null
+++ b/src/ui/touch.h
@@ -0,0 +1,32 @@
+/* Copyright 2021 Jaakko Keränen jaakko.keranen@iki.fi
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+1. Redistributions of source code must retain the above copyright notice, this
+2. Redistributions in binary form must reproduce the above copyright notice,
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+#pragma once
+#include <the_Foundation/defs.h>
+#include <SDL_events.h>
+iDeclareType(Widget)
+iBool processEvent_Touch (const SDL_Event *);
+void update_Touch (void);
+void widgetDestroyed_Touch (iWidget *widget);
diff --git a/src/ui/util.c b/src/ui/util.c
index d64a93b6..3e57c2b5 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -37,6 +37,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "text.h"
#include "window.h"
+#if defined (iPlatformAppleMobile)
+# include "../ios.h"
+#endif
#include <the_Foundation/math.h>
#include <the_Foundation/path.h>
#include <SDL_timer.h>
@@ -194,14 +198,18 @@ static float valueAt_Anim_(const iAnim *d, const uint32_t now) {
return d->from;
}
float t = pos_Anim_(d, now);
if ((d->flags & easeBoth_AnimFlag) == easeBoth_AnimFlag) {
t = easeBoth_(t);
if (isSoft) t = easeBoth_(t);
}
else if (d->flags & easeIn_AnimFlag) {
t = easeIn_(t);
if (isSoft) t = easeIn_(t);
}
else if (d->flags & easeOut_AnimFlag) {
t = easeOut_(t);
if (isSoft) t = easeOut_(t);
}
return d->from * (1.0f - t) + d->to * t;
}
@@ -354,13 +362,13 @@ iLabelWidget *makeHeading_Widget(const char *text) {
iWidget *makeVDiv_Widget(void) {
iWidget *div = new_Widget();
return div;
}
iWidget *makeHDiv_Widget(void) {
iWidget *div = new_Widget();
return div;
}
@@ -374,11 +382,21 @@ iWidget *addAction_Widget(iWidget *parent, int key, int kmods, const char *comma
/-----------------------------------------------------------------------------------------------/
static iBool isCommandIgnoredByMenus_(const char *cmd) {
be reacted to by menus? */
equal_Command(cmd, "media.player.update") ||
startsWith_CStr(cmd, "feeds.update.") ||
equal_Command(cmd, "document.request.updated") ||
equal_Command(cmd, "bookmarks.request.started") ||
equal_Command(cmd, "bookmarks.request.finished") ||
equal_Command(cmd, "window.resized") ||
equal_Command(cmd, "document.autoreload") ||
equal_Command(cmd, "document.reload") ||
equal_Command(cmd, "document.request.started") ||
equal_Command(cmd, "document.request.updated") ||
equal_Command(cmd, "document.request.finished") ||
equal_Command(cmd, "document.changed") ||
equal_Command(cmd, "visited.changed") ||
(deviceType_App() == desktop_AppDeviceType && equal_Command(cmd, "window.resized")) ||
equal_Command(cmd, "window.reload.update") ||
equal_Command(cmd, "window.mouse.exited") ||
equal_Command(cmd, "window.mouse.entered") ||
@@ -420,10 +438,24 @@ static iBool menuHandler_(iWidget *menu, const char *cmd) {
return iFalse;
}
+static iWidget *makeMenuSeparator_(void) {
sep->rect.size.y = gap_UI / 2;
+}
iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
iWidget *menu = new_Widget();
setFrameColor_Widget(menu, uiSeparator_ColorId);
setBackgroundColor_Widget(menu, uiBackground_ColorId);
setPadding1_Widget(menu, gap_UI);
setFlags_Widget(menu,
keepOnTop_WidgetFlag | collapse_WidgetFlag | hidden_WidgetFlag |
arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag |
@@ -432,10 +464,7 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
for (size_t i = 0; i < n; ++i) {
const iMenuItem *item = &items[i];
if (equal_CStr(item->label, "---")) {
iWidget *sep = addChild_Widget(menu, iClob(new_Widget()));
setBackgroundColor_Widget(sep, uiSeparator_ColorId);
sep->rect.size.y = gap_UI / 3;
setFlags_Widget(sep, hover_WidgetFlag | fixedHeight_WidgetFlag, iTrue);
addChild_Widget(menu, iClob(makeMenuSeparator_()));
}
else {
iLabelWidget *label = addChildFlags_Widget(
@@ -443,8 +472,21 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
iClob(newKeyMods_LabelWidget(item->label, item->key, item->kmods, item->command)),
frameless_WidgetFlag | alignLeft_WidgetFlag | drawKey_WidgetFlag);
updateSize_LabelWidget(label); /* drawKey was set */
const iBool isCaution = startsWith_CStr(item->label, uiTextCaution_ColorEscape);
if (deviceType_App() == tablet_AppDeviceType) {
setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId);
}
else if (deviceType_App() == desktop_AppDeviceType) {
setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId);
}
}
}
addChild_Widget(menu, iClob(makeMenuSeparator_()));
setFont_LabelWidget(addChildFlags_Widget(menu, iClob(new_LabelWidget("Cancel", "cancel")),
frameless_WidgetFlag | alignLeft_WidgetFlag),
defaultBig_FontId);
addChild_Widget(parent, iClob(menu));
setCommandHandler_Widget(menu, menuHandler_);
iWidget *cancel = addAction_Widget(menu, SDLK_ESCAPE, 0, "cancel");
@@ -454,21 +496,48 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
}
void openMenu_Widget(iWidget *d, iInt2 coord) {
/* Menu closes when commands are emitted, so handle any pending ones beforehand. */
postCommand_App("cancel"); /* dismiss any other menus */
processEvents_App(postedEventsOnly_AppEventMode);
setFlags_Widget(d, hidden_WidgetFlag, iFalse);
setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue);
setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse);
setFlags_Widget(d, arrangeWidth_WidgetFlag | resizeChildrenToWidestChild_WidgetFlag, iFalse);
setFlags_Widget(d, resizeWidthOfChildren_WidgetFlag, iTrue);
d->rect.size.x = rootSize_Window(get_Window()).x;
iForEach(ObjectList, i, children_Widget(d)) {
if (isInstance_Object(i.object, &Class_LabelWidget)) {
iLabelWidget *label = i.object;
setFont_LabelWidget(label, defaultBig_FontId);
}
}
arrange_Widget(d);
d->rect.pos = init_I2(0, rootSize.y);
d->rect.pos = coord;
/* Ensure the full menu is visible. */
const iRect bounds = bounds_Widget(d);
+#if defined (iPlatformAppleMobile)
float l, t, r, b;
safeAreaInsets_iOS(&l, &t, &r, &b);
topExcess += t;
bottomExcess += b;
leftExcess += l;
rightExcess += r;
+#endif
if (bottomExcess > 0) {
d->rect.pos.y -= bottomExcess;
}
@@ -483,6 +552,10 @@ void openMenu_Widget(iWidget *d, iInt2 coord) {
}
postRefresh_App();
postCommand_Widget(d, "menu.opened");
setVisualOffset_Widget(d, height_Widget(d), 0, 0);
setVisualOffset_Widget(d, 0, 330, easeOut_AnimFlag | softer_AnimFlag);
}
void closeMenu_Widget(iWidget *d) {
@@ -600,7 +673,8 @@ static void addTabPage_Widget_(iWidget *tabs, enum iWidgetAddPos addPos, iWidget
addPos);
setFlags_Widget(buttons, hidden_WidgetFlag, iFalse);
setFlags_Widget(button, selected_WidgetFlag, isSel);
button, noTopFrame_WidgetFlag | commandOnClick_WidgetFlag | expand_WidgetFlag, iTrue);
addChildPos_Widget(pages, page, addPos);
setFlags_Widget(page, hidden_WidgetFlag | disabled_WidgetFlag, !isSel);
}
@@ -764,7 +838,8 @@ iWidget *makeSheet_Widget(const char *id) {
setFrameColor_Widget(sheet, uiSeparator_ColorId);
setBackgroundColor_Widget(sheet, uiBackground_ColorId);
setFlags_Widget(sheet,
focusRoot_WidgetFlag | mouseModal_WidgetFlag | keepOnTop_WidgetFlag |
parentCannotResize_WidgetFlag |
focusRoot_WidgetFlag | mouseModal_WidgetFlag | keepOnTop_WidgetFlag |
arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag |
centerHorizontal_WidgetFlag | overflowScrollable_WidgetFlag,
iTrue);
@@ -772,7 +847,7 @@ iWidget *makeSheet_Widget(const char *id) {
}
void centerSheet_Widget(iWidget *sheet) {
postRefresh_App();
}
@@ -833,7 +908,7 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) {
return iFalse;
}
if (equal_Command(cmd, "input.ended")) {
if (hasParent_Widget(ptr, dlg)) {
if (argLabel_Command(cmd, "enter") && hasParent_Widget(ptr, dlg)) {
if (arg_Command(cmd)) {
acceptValueInput_(dlg);
}
@@ -860,11 +935,62 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) {
return iFalse;
}
+static iWidget *makeDialogButtons_(const iMenuItem *actions, size_t numActions) {
arrangeHorizontal_WidgetFlag | arrangeHeight_WidgetFlag |
resizeToParentWidth_WidgetFlag |
resizeWidthOfChildren_WidgetFlag,
iTrue);
if (!iCmpStr(actions[i].label, "---")) {
haveSep = iTrue;
break;
}
addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag);
const char *label = actions[i].label;
const char *cmd = actions[i].command;
int key = actions[i].key;
int kmods = actions[i].kmods;
const iBool isDefault = (i == numActions - 1);
if (!iCmpStr(label, "---")) {
/* Separator.*/
addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag);
continue;
}
if (!iCmpStr(label, "Cancel") && !cmd) {
cmd = "cancel";
key = SDLK_ESCAPE;
kmods = 0;
}
if (isDefault) {
if (!key) {
key = SDLK_RETURN;
kmods = 0;
}
if (label == NULL) {
label = uiTextAction_ColorEscape " OK ";
}
}
iLabelWidget *button =
addChild_Widget(div, iClob(newKeyMods_LabelWidget(actions[i].label, key, kmods, cmd)));
if (isDefault) {
setFont_LabelWidget(button, uiLabelBold_FontId);
}
+}
iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, const char *title,
const char *prompt, const char *acceptLabel, const char *command) {
if (parent) {
setFocus_Widget(NULL);
-// processEvents_App(postedEventsOnly_AppEventMode);
}
iWidget *dlg = makeSheet_Widget(command);
setCommandHandler_Widget(dlg, valueInputHandler_);
@@ -884,18 +1010,11 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con
setId_Widget(as_Widget(input), "input");
updateValueInputWidth_(dlg);
addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
addChild_Widget(div, iClob(newKeyMods_LabelWidget("Cancel", SDLK_ESCAPE, 0, "cancel")));
iLabelWidget *accept = addChild_Widget(
div,
iClob(newKeyMods_LabelWidget(acceptLabel ? acceptLabel : uiTextAction_ColorEscape "OK",
SDLK_RETURN,
0,
"valueinput.accept")));
setFont_LabelWidget(accept, uiLabelBold_FontId);
dlg,
iClob(makeDialogButtons_(
(iMenuItem[]){ { "Cancel", 0, 0, NULL }, { acceptLabel, 0, 0, "valueinput.accept" } },
2)));
centerSheet_Widget(dlg);
if (parent) {
setFocus_Widget(as_Widget(input));
@@ -914,42 +1033,35 @@ static iBool messageHandler_(iWidget *msg, const char *cmd) {
if (!(equal_Command(cmd, "media.updated") ||
equal_Command(cmd, "media.player.update") ||
equal_Command(cmd, "bookmarks.request.finished") ||
equal_Command(cmd, "document.request.updated") || startsWith_CStr(cmd, "window."))) {
equal_Command(cmd, "document.autoreload") ||
equal_Command(cmd, "document.reload") ||
equal_Command(cmd, "document.request.updated") ||
startsWith_CStr(cmd, "window."))) {
destroy_Widget(msg);
}
return iFalse;
}
iWidget *makeMessage_Widget(const char *title, const char *msg) {
title, msg, (const char *[]){ "Continue" }, (const char *[]){ "message.ok" }, 1);
makeQuestion_Widget(title, msg, (iMenuItem[]){ { "Continue", 0, 0, "message.ok" } }, 1);
addAction_Widget(dlg, SDLK_ESCAPE, 0, "message.ok");
addAction_Widget(dlg, SDLK_SPACE, 0, "message.ok");
return dlg;
}
-iWidget *makeQuestion_Widget(const char *title, const char *msg, const char *labels[],
const char *commands[], size_t count) {
+iWidget *makeQuestion_Widget(const char *title, const char *msg,
const iMenuItem *items, size_t numItems) {
processEvents_App(postedEventsOnly_AppEventMode);
iWidget *dlg = makeSheet_Widget("");
setCommandHandler_Widget(dlg, messageHandler_);
addChildFlags_Widget(dlg, iClob(new_LabelWidget(title, NULL)), frameless_WidgetFlag);
addChildFlags_Widget(dlg, iClob(new_LabelWidget(msg, NULL)), frameless_WidgetFlag);
addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
for (size_t i = 0; i < count; ++i) {
/* The last one is the default option. */
const int key = (i == count - 1 ? SDLK_RETURN : 0);
iLabelWidget *btn =
addChild_Widget(div, iClob(newKeyMods_LabelWidget(labels[i], key, 0, commands[i])));
if (key) {
setFont_LabelWidget(btn, uiLabelBold_FontId);
}
}
addChild_Widget(get_Window()->root, iClob(dlg));
be arranged correctly unless it's here. */
centerSheet_Widget(dlg);
return dlg;
}
@@ -1037,14 +1149,18 @@ static void addRadioButton_(iWidget *parent, const char *id, const char *label,
static void addFontButtons_(iWidget *parent, const char *id) {
const char *fontNames[] = {
"Nunito", "Fira Sans", "Literata", "Tinos"
"Nunito", "Fira Sans", "Literata", "Tinos", "Source Sans Pro", "Iosevka"
};
iForIndices(i, fontNames) {
addRadioButton_(parent,
format_CStr("prefs.%s.%u", id, i),
fontNames[i],
format_CStr("%s.set arg:%u", id, i));
pushBack_Array(items,
&(iMenuItem){ fontNames[i], 0, 0, format_CStr("!%s.set arg:%d", id, i) });
}
+// setFrameColor_Widget(findChild_Widget(as_Widget(button), "menu"), uiBackgroundSelected_ColorId);
}
iWidget *makePreferences_Widget(void) {
@@ -1063,6 +1179,9 @@ iWidget *makePreferences_Widget(void) {
#endif
addChild_Widget(headings, iClob(makeHeading_Widget("Show URL on hover:")));
addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink")));
addChild_Widget(headings, iClob(makeHeading_Widget("Vertical centering:")));
addChild_Widget(values, iClob(makeToggle_Widget("prefs.centershort")));
makeTwoColumnHeading_("SCROLLING", headings, values);
addChild_Widget(headings, iClob(makeHeading_Widget("Smooth scrolling:")));
addChild_Widget(values, iClob(makeToggle_Widget("prefs.smoothscroll")));
addChild_Widget(headings, iClob(makeHeading_Widget("Load image on scroll:")));
@@ -1083,10 +1202,22 @@ iWidget *makePreferences_Widget(void) {
setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("Pure White", "theme.set arg:3"))), "prefs.theme.3");
}
addChildFlags_Widget(values, iClob(themes), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
addChild_Widget(headings, iClob(makeHeading_Widget("Retain window size:")));
addChild_Widget(values, iClob(makeToggle_Widget("prefs.retainwindow")));
/* Accents. */
iWidget *accent = new_Widget(); {
setId_Widget(addChild_Widget(accent, iClob(new_LabelWidget("Teal", "accent.set arg:0"))), "prefs.accent.0");
setId_Widget(addChild_Widget(accent, iClob(new_LabelWidget("Orange", "accent.set arg:1"))), "prefs.accent.1");
}
addChild_Widget(headings, iClob(makeHeading_Widget("Accent color:")));
addChildFlags_Widget(values, iClob(accent), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
+#if defined (LAGRANGE_CUSTOM_FRAME)
addChild_Widget(headings, iClob(makeHeading_Widget("Custom window frame:")));
addChild_Widget(values, iClob(makeToggle_Widget("prefs.customframe")));
+#endif
makeTwoColumnHeading_("SIZING", headings, values);
addChild_Widget(headings, iClob(makeHeading_Widget("UI scale factor:")));
setId_Widget(addChild_Widget(values, iClob(new_InputWidget(8))), "prefs.uiscale");
addChild_Widget(headings, iClob(makeHeading_Widget("Retain placement:")));
addChild_Widget(values, iClob(makeToggle_Widget("prefs.retainwindow")));
makeTwoColumnHeading_("WIDE LAYOUT", headings, values);
addChild_Widget(headings, iClob(makeHeading_Widget("Site icon:")));
addChild_Widget(values, iClob(makeToggle_Widget("prefs.sideicon")));
@@ -1107,18 +1238,20 @@ iWidget *makePreferences_Widget(void) {
{ "High Contrast", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, highContrast_GmDocumentTheme) },
};
addChild_Widget(headings, iClob(makeHeading_Widget(isDark ? "Dark theme:" : "Light theme:")));
setId_Widget(addChild_Widget(values,
iClob(makeMenuButton_LabelWidget(
themes[0].label, themes, iElemCount(themes)))),
iLabelWidget *button =
makeMenuButton_LabelWidget(themes[1].label, themes, iElemCount(themes));
+// setFrameColor_Widget(findChild_Widget(as_Widget(button), "menu"),
+// uiBackgroundSelected_ColorId);
setId_Widget(addChildFlags_Widget(values, iClob(button), alignLeft_WidgetFlag),
format_CStr("prefs.doctheme.%s", mode));
}
addChild_Widget(headings, iClob(makeHeading_Widget("Saturation:")));
iWidget *sats = new_Widget();
/* Saturation levels. */ {
addRadioButton_(sats, "prefs.saturation.3", "Full", "saturation.set arg:100");
addRadioButton_(sats, "prefs.saturation.2", "Reduced", "saturation.set arg:66");
addRadioButton_(sats, "prefs.saturation.1", "Minimal", "saturation.set arg:33");
addRadioButton_(sats, "prefs.saturation.0", "Monochrome", "saturation.set arg:0");
addRadioButton_(sats, "prefs.saturation.3", "100 %%", "saturation.set arg:100");
addRadioButton_(sats, "prefs.saturation.2", "66 %%", "saturation.set arg:66");
addRadioButton_(sats, "prefs.saturation.1", "33 %%", "saturation.set arg:33");
addRadioButton_(sats, "prefs.saturation.0", "0 %%", "saturation.set arg:0");
}
addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
}
@@ -1128,13 +1261,13 @@ iWidget *makePreferences_Widget(void) {
/* Fonts. */ {
iWidget *fonts;
addChild_Widget(headings, iClob(makeHeading_Widget("Heading font:")));
fonts = new_Widget();
addFontButtons_(fonts, "headingfont");
addChildFlags_Widget(values, iClob(fonts), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
+// fonts = new_Widget();
addFontButtons_(values, "headingfont");
+// addChildFlags_Widget(values, iClob(fonts), 0); //arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
addChild_Widget(headings, iClob(makeHeading_Widget("Body font:")));
fonts = new_Widget();
addFontButtons_(fonts, "font");
addChildFlags_Widget(values, iClob(fonts), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
+// fonts = new_Widget();
addFontButtons_(values, "font");
+// addChildFlags_Widget(values, iClob(fonts), 0); //arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
addChild_Widget(headings, iClob(makeHeading_Widget("Monospace body:")));
iWidget *mono = new_Widget();
/* TODO: Needs labels! */
@@ -1167,6 +1300,10 @@ iWidget *makePreferences_Widget(void) {
}
/* Network. */ {
appendTwoColumnPage_(tabs, "Network", '5', &headings, &values);
addChild_Widget(headings, iClob(makeHeading_Widget("Search URL:")));
setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.searchurl");
addChild_Widget(headings, iClob(makeHeading_Widget("Decode URLs:")));
addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls")));
addChild_Widget(headings, iClob(makeHeading_Widget("Cache size:")));
iWidget *cacheGroup = new_Widget(); {
iInputWidget *cache = new_InputWidget(4);
@@ -1175,8 +1312,6 @@ iWidget *makePreferences_Widget(void) {
addChildFlags_Widget(cacheGroup, iClob(new_LabelWidget("MB", NULL)), frameless_WidgetFlag);
}
addChildFlags_Widget(values, iClob(cacheGroup), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
addChild_Widget(headings, iClob(makeHeading_Widget("Decode URLs:")));
addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls")));
makeTwoColumnHeading_("PROXIES", headings, values);
addChild_Widget(headings, iClob(makeHeading_Widget("Gemini proxy:")));
setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.gemini");
@@ -1190,24 +1325,21 @@ iWidget *makePreferences_Widget(void) {
setFlags_Widget(as_Widget(bind), borderTop_WidgetFlag, iTrue);
appendFramelessTabPage_(tabs, iClob(bind), "Keys", '6', KMOD_PRIMARY);
}
resizeToLargestPage_Widget(tabs);
arrange_Widget(dlg);
/* Set input field sizes. */ {
expandInputFieldWidth_(findChild_Widget(tabs, "prefs.searchurl"));
expandInputFieldWidth_(findChild_Widget(tabs, "prefs.downloads"));
expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.gemini"));
expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.gopher"));
expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.http"));
}
setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
setFont_LabelWidget(
addChild_Widget(
div, iClob(newKeyMods_LabelWidget("Dismiss", SDLK_ESCAPE, 0, "prefs.dismiss"))),
uiLabelBold_FontId);
iClob(makeDialogButtons_(
(iMenuItem[]){ { "Dismiss", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1)));
addChild_Widget(get_Window()->root, iClob(dlg));
return dlg;
}
@@ -1225,7 +1357,7 @@ iWidget *makeBookmarkEditor_Widget(void) {
page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
iWidget *values = addChildFlags_Widget(
page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
addChild_Widget(headings, iClob(makeHeading_Widget("Title:")));
setId_Widget(addChild_Widget(values, iClob(inputs[0] = new_InputWidget(0))), "bmed.title");
addChild_Widget(headings, iClob(makeHeading_Widget("URL:")));
@@ -1233,20 +1365,20 @@ iWidget *makeBookmarkEditor_Widget(void) {
setUrlContent_InputWidget(inputs[1], iTrue);
addChild_Widget(headings, iClob(makeHeading_Widget("Tags:")));
setId_Widget(addChild_Widget(values, iClob(inputs[2] = new_InputWidget(0))), "bmed.tags");
arrange_Widget(dlg);
for (int i = 0; i < 3; ++i) {
as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x;
}
setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
addChild_Widget(div, iClob(newKeyMods_LabelWidget("Cancel", SDLK_ESCAPE, 0, "cancel")));
iLabelWidget *accept = addChild_Widget(
div,
iClob(newKeyMods_LabelWidget(
uiTextCaution_ColorEscape "Save Bookmark", SDLK_RETURN, KMOD_PRIMARY, "bmed.accept")));
setFont_LabelWidget(accept, uiLabelBold_FontId);
dlg,
iClob(makeDialogButtons_((iMenuItem[]){ { "Cancel", 0, 0, NULL },
{ uiTextCaution_ColorEscape "Save Bookmark",
SDLK_RETURN,
KMOD_PRIMARY,
"bmed.accept" } },
2)));
addChild_Widget(get_Window()->root, iClob(dlg));
centerSheet_Widget(dlg);
return dlg;
@@ -1263,11 +1395,14 @@ static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, cons
const iString *title = text_InputWidget(findChild_Widget(editor, "bmed.title"));
const iString *url = text_InputWidget(findChild_Widget(editor, "bmed.url"));
const iString *tags = text_InputWidget(findChild_Widget(editor, "bmed.tags"));
add_Bookmarks(bookmarks_App(),
url,
title,
tags,
first_String(text_LabelWidget(findChild_Widget(editor, "bmed.icon"))));
const iString *icon = collect_String(trimmed_String(text_LabelWidget(findChild_Widget(editor, "bmed.icon"))));
const uint32_t id = add_Bookmarks(bookmarks_App(), url, title, tags, first_String(icon));
if (!isEmpty_String(icon)) {
iBookmark *bm = get_Bookmarks(bookmarks_App(), id);
if (!hasTag_Bookmark(bm, "usericon")) {
addTag_Bookmark(bm, "usericon");
}
}
postCommand_App("bookmarks.changed");
}
destroy_Widget(editor);
@@ -1370,20 +1505,17 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
addRadioButton_(types, "feedcfg.type.headings", "New Headings", "feedcfg.type arg:1");
}
addChildFlags_Widget(values, iClob(types), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
addChild_Widget(div, iClob(newKeyMods_LabelWidget("Cancel", SDLK_ESCAPE, 0, "cancel")));
setId_Widget(addChild_Widget(div,
iClob(newKeyMods_LabelWidget(
bookmarkId ? uiTextCaution_ColorEscape "Save Settings"
: uiTextCaution_ColorEscape "Subscribe",
SDLK_RETURN,
KMOD_PRIMARY,
format_CStr("feedcfg.accept bmid:%d", bookmarkId)))),
"feedcfg.save");
setFont_LabelWidget(findChild_Widget(div, "feedcfg.save"), uiLabelBold_FontId);
addChild_Widget(dlg,
iClob(makeDialogButtons_(
(iMenuItem[]){ { "Cancel", 0, 0, NULL },
{ bookmarkId ? uiTextCaution_ColorEscape "Save Settings"
: uiTextCaution_ColorEscape "Subscribe",
SDLK_RETURN,
KMOD_PRIMARY,
format_CStr("feedcfg.accept bmid:%d", bookmarkId) } },
2)));
arrange_Widget(dlg);
as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x;
addChild_Widget(get_Window()->root, iClob(dlg));
@@ -1455,16 +1587,14 @@ iWidget *makeIdentityCreation_Widget(void) {
for (size_t i = 0; i < iElemCount(inputs); ++i) {
as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x;
}
setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
addChild_Widget(div, iClob(newKeyMods_LabelWidget("Cancel", SDLK_ESCAPE, 0, "cancel")));
iLabelWidget *accept = addChild_Widget(
div,
iClob(newKeyMods_LabelWidget(
uiTextAction_ColorEscape "Create Identity", SDLK_RETURN, KMOD_PRIMARY, "ident.accept")));
setFont_LabelWidget(accept, uiLabelBold_FontId);
dlg,
iClob(makeDialogButtons_((iMenuItem[]){ { "Cancel", 0, 0, NULL },
{ uiTextAction_ColorEscape "Create Identity",
SDLK_RETURN,
KMOD_PRIMARY,
"ident.accept" } },
2)));
addChild_Widget(get_Window()->root, iClob(dlg));
centerSheet_Widget(dlg);
return dlg;
diff --git a/src/ui/util.h b/src/ui/util.h
index a280fedb..96e08e95 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -75,7 +75,8 @@ enum iAnimFlag {
indefinite_AnimFlag = iBit(1), /* does not end; must be linear */
easeIn_AnimFlag = iBit(2),
easeOut_AnimFlag = iBit(3),
};
struct Impl_Anim {
@@ -198,7 +199,7 @@ iWidget * makeValueInput_Widget (iWidget *parent, const iString *initialValu
void updateValueInput_Widget (iWidget *, const char *title, const char *prompt);
iWidget * makeMessage_Widget (const char *title, const char *msg);
iWidget * makeQuestion_Widget (const char *title, const char *msg,
const char *labels[], const char *commands[], size_t count);
const iMenuItem *items, size_t numItems);
iWidget * makePreferences_Widget (void);
iWidget * makeBookmarkEditor_Widget (void);
diff --git a/src/ui/widget.c b/src/ui/widget.c
index ddb3f092..386ba6d6 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "widget.h"
#include "app.h"
+#include "touch.h"
#include "command.h"
#include "paint.h"
#include "util.h"
@@ -77,6 +78,7 @@ void init_Widget(iWidget *d) {
d->rect = zero_Rect();
d->bgColor = none_ColorId;
d->frameColor = none_ColorId;
d->children = NULL;
d->parent = NULL;
d->commandHandler = NULL;
@@ -86,6 +88,11 @@ void init_Widget(iWidget *d) {
void deinit_Widget(iWidget *d) {
releaseChildren_Widget(d);
deinit_String(&d->id);
+// printf("widget %p deleted (on top:%d)\n", d, d->flags & keepOnTop_WidgetFlag ? 1 : 0);
removeAll_PtrArray(onTop_RootData_(), d);
}
static void aboutToBeDestroyed_Widget_(iWidget *d) {
@@ -126,11 +133,15 @@ const iString *id_Widget(const iWidget *d) {
}
int64_t flags_Widget(const iWidget *d) {
}
void setFlags_Widget(iWidget *d, int64_t flags, iBool set) {
if (d) {
if (deviceType_App() == phone_AppDeviceType) {
/* Phones rarely have keyboards attached so don't bother with the shortcuts. */
flags &= ~drawKey_WidgetFlag;
}
iChangeFlags(d->flags, flags, set);
if (flags & keepOnTop_WidgetFlag) {
if (set) {
@@ -149,6 +160,8 @@ void setPos_Widget(iWidget *d, iInt2 pos) {
}
void setSize_Widget(iWidget *d, iInt2 size) {
d->rect.size = size;
setFlags_Widget(d, fixedSize_WidgetFlag, iTrue);
}
@@ -160,8 +173,33 @@ void setPadding_Widget(iWidget *d, int left, int top, int right, int bottom) {
d->padding[3] = bottom;
}
+static void visualOffsetAnimation_Widget_(void *ptr) {
addTicker_App(visualOffsetAnimation_Widget_, ptr);
setFlags_Widget(d, visualOffset_WidgetFlag, iFalse);
+}
+void setVisualOffset_Widget(iWidget *d, int value, uint32_t span, int animFlags) {
init_Anim(&d->visualOffset, value);
setValue_Anim(&d->visualOffset, value, span);
d->visualOffset.flags = animFlags;
addTicker_App(visualOffsetAnimation_Widget_, d);
+}
void setBackgroundColor_Widget(iWidget *d, int bgColor) {
d->bgColor = bgColor;
}
void setFrameColor_Widget(iWidget *d, int frameColor) {
@@ -296,7 +334,7 @@ void arrange_Widget(iWidget *d) {
iWidget *child = as_Widget(c.object);
if (isCollapsed_Widget_(child)) {
if (d->flags & arrangeHorizontal_WidgetFlag) {
setWidth_Widget_(child, 0);
setWidth_Widget_(child, 0);
}
if (d->flags & arrangeVertical_WidgetFlag) {
setHeight_Widget_(child, 0);
@@ -331,8 +369,8 @@ void arrange_Widget(iWidget *d) {
avail = divi_I2(max_I2(zero_I2(), avail), expCount);
iForEach(ObjectList, j, d->children) {
iWidget *child = as_Widget(j.object);
if (isCollapsed_Widget_(child)) continue;
if (child->flags & fixedPosition_WidgetFlag) {
if (isCollapsed_Widget_(child) ||
child->flags & (parentCannotResize_WidgetFlag | fixedPosition_WidgetFlag)) {
continue;
}
if (child->flags & expand_WidgetFlag) {
@@ -367,7 +405,7 @@ void arrange_Widget(iWidget *d) {
}
iForEach(ObjectList, i, d->children) {
iWidget *child = as_Widget(i.object);
if (!isCollapsed_Widget_(child)) {
if (!isCollapsed_Widget_(child) && ~child->flags & parentCannotResize_WidgetFlag) {
if (dirs.x) setWidth_Widget_(child, childSize.x);
if (dirs.y) setHeight_Widget_(child, childSize.y);
}
@@ -450,8 +488,15 @@ void arrange_Widget(iWidget *d) {
iRect bounds_Widget(const iWidget *d) {
iRect bounds = d->rect;
bounds.pos.y += iRound(value_Anim(&d->visualOffset));
for (const iWidget *w = d->parent; w; w = w->parent) {
addv_I2(&bounds.pos, w->rect.pos);
iInt2 pos = w->rect.pos;
if (w->flags & visualOffset_WidgetFlag) {
pos.y += iRound(value_Anim(&w->visualOffset));
}
addv_I2(&bounds.pos, pos);
}
return bounds;
}
@@ -623,7 +668,9 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
}
void drawBackground_Widget(const iWidget *d) {
return;
if (flags_Widget(d) & borderTop_WidgetFlag) {
const iRect rect = bounds_Widget(d);
iPaint p;
@@ -631,10 +678,56 @@ void drawBackground_Widget(const iWidget *d) {
drawHLine_Paint(&p, topLeft_Rect(rect), width_Rect(rect), uiBackgroundFramelessHover_ColorId);
}
if (d->bgColor >= 0 || d->frameColor >= 0) {
const iRect rect = bounds_Widget(d);
iRect rect = bounds_Widget(d);
iPaint p;
init_Paint(&p);
if (d->flags & mouseModal_WidgetFlag) {
p.alpha = 0x60;
SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
int fadeColor;
switch (colorTheme_App()) {
default:
fadeColor = black_ColorId;
break;
case light_ColorTheme:
fadeColor = gray25_ColorId;
break;
case pureWhite_ColorTheme:
fadeColor = gray50_ColorId;
break;
}
fillRect_Paint(&p,
initCorners_Rect(zero_I2(), rootSize_Window(get_Window())),
fadeColor);
SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
}
if (d->bgColor >= 0) {
+#if defined (iPlatformAppleMobile)
if (d->flags & (drawBackgroundToHorizontalSafeArea_WidgetFlag |
drawBackgroundToVerticalSafeArea_WidgetFlag)) {
const iInt2 rootSize = rootSize_Window(get_Window());
const iInt2 center = divi_I2(rootSize, 2);
int top = 0, right = 0, bottom = 0, left = 0;
if (d->flags & drawBackgroundToHorizontalSafeArea_WidgetFlag) {
const iBool isWide = width_Rect(rect) > rootSize.x * 9 / 10;
if (isWide || mid_Rect(rect).x < center.x) {
left = -left_Rect(rect);
}
if (isWide || mid_Rect(rect).x > center.x) {
right = rootSize.x - right_Rect(rect);
}
}
if (d->flags & drawBackgroundToVerticalSafeArea_WidgetFlag) {
if (top_Rect(rect) > center.y) {
bottom = rootSize.y - bottom_Rect(rect);
}
if (bottom_Rect(rect) < center.y) {
top = -top_Rect(rect);
}
}
adjustEdges_Rect(&rect, top, right, bottom, left);
}
+#endif
fillRect_Paint(&p, rect, d->bgColor);
}
if (d->frameColor >= 0 && ~d->flags & frameless_WidgetFlag) {
@@ -643,11 +736,17 @@ void drawBackground_Widget(const iWidget *d) {
}
}
+iLocalDef iBool isDrawn_Widget_(const iWidget *d) {
+}
void drawChildren_Widget(const iWidget *d) {
return;
iConstForEach(ObjectList, i, d->children) {
const iWidget *child = constAs_Widget(i.object);
if (~child->flags & keepOnTop_WidgetFlag && ~child->flags & hidden_WidgetFlag) {
if (~child->flags & keepOnTop_WidgetFlag && isDrawn_Widget_(child)) {
class_Widget(child)->draw(child);
}
}
@@ -686,6 +785,28 @@ iAny *addChildPos_Widget(iWidget *d, iAnyObject *child, enum iWidgetAddPos addPo
return child;
}
+iAny *insertChildAfter_Widget(iWidget *d, iAnyObject *child, size_t afterIndex) {
if (afterIndex-- == 0) {
insertAfter_ObjectList(d->children, i.value, child);
break;
}
+}
+iAny *insertChildAfterFlags_Widget(iWidget *d, iAnyObject *child, size_t afterIndex, int64_t childFlags) {
+}
iAny *addChildFlags_Widget(iWidget *d, iAnyObject *child, int64_t childFlags) {
setFlags_Widget(child, childFlags, iTrue);
return addChild_Widget(d, child);
@@ -729,11 +850,27 @@ size_t childIndex_Widget(const iWidget *d, const iAnyObject *child) {
}
iAny *hitChild_Widget(const iWidget *d, iInt2 coord) {
iAny *found = hitChild_Widget(constAs_Widget(i.object), coord);
if (found) return found;
return NULL;
iForEach(PtrArray, i, onTop_RootData_()) {
iAny *found = hitChild_Widget(constAs_Widget(i.ptr), coord);
if (found) return found;
}
const iWidget *child = constAs_Widget(i.object);
if (~child->flags & keepOnTop_WidgetFlag) {
iAny *found = hitChild_Widget(child, coord);
if (found) return found;
}
return NULL; /* Plain widgets are not hittable. */
}
return iConstCast(iWidget *, d);
}
return NULL;
diff --git a/src/ui/widget.h b/src/ui/widget.h
index 79f68b3c..c5a1a360 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/* Base class for UI widgets. */
#include "metrics.h"
+#include "util.h"
#include <the_Foundation/object.h>
#include <the_Foundation/objectlist.h>
@@ -91,6 +92,14 @@ enum iWidgetFlag {
#define borderTop_WidgetFlag iBit64(37)
#define overflowScrollable_WidgetFlag iBit64(38)
#define focusRoot_WidgetFlag iBit64(39)
+#define unhittable_WidgetFlag iBit64(40)
+#define touchDrag_WidgetFlag iBit64(41) /* touch event behavior: immediate drag */
+#define noBackground_WidgetFlag iBit64(42)
+#define drawBackgroundToHorizontalSafeArea_WidgetFlag iBit64(43)
+#define drawBackgroundToVerticalSafeArea_WidgetFlag iBit64(44)
+#define visualOffset_WidgetFlag iBit64(45)
+#define parentCannotResize_WidgetFlag iBit64(46)
+#define noTopFrame_WidgetFlag iBit64(47)
enum iWidgetAddPos {
back_WidgetAddPos,
@@ -108,6 +117,7 @@ struct Impl_Widget {
int64_t flags;
iRect rect;
int padding[4]; /* left, top, right, bottom */
int bgColor;
int frameColor;
iObjectList *children;
@@ -157,12 +167,18 @@ void drawBackground_Widget(const iWidget *);
void drawChildren_Widget (const iWidget *);
iLocalDef int width_Widget(const iAnyObject *d) {
iAssert(isInstance_Object(d, &Class_Widget));
return ((const iWidget *) d)->rect.size.x;
}
iLocalDef int height_Widget(const iAnyObject *d) {
iAssert(isInstance_Object(d, &Class_Widget));
return ((const iWidget *) d)->rect.size.y;
}
iLocalDef iObjectList *children_Widget(iAnyObject *d) {
iAssert(isInstance_Object(d, &Class_Widget));
@@ -189,12 +205,15 @@ void setPos_Widget (iWidget *, iInt2 pos);
void setSize_Widget (iWidget *, iInt2 size);
void setPadding_Widget (iWidget *, int left, int top, int right, int bottom);
iLocalDef void setPadding1_Widget (iWidget *d, int padding) { setPadding_Widget(d, padding, padding, padding, padding); }
+void setVisualOffset_Widget (iWidget *d, int value, uint32_t span, int animFlags);
void setBackgroundColor_Widget (iWidget *, int bgColor);
void setFrameColor_Widget (iWidget *, int frameColor);
void setCommandHandler_Widget (iWidget *, iBool (*handler)(iWidget *, const char *));
-iAny * addChild_Widget (iWidget *, iAnyObject child); / holds a ref */
-iAny * addChildPos_Widget (iWidget *, iAnyObject *child, enum iWidgetAddPos addPos);
-iAny * addChildFlags_Widget(iWidget *, iAnyObject child, int64_t childFlags); / holds a ref */
+iAny * addChild_Widget (iWidget *, iAnyObject child); / holds a ref */
+iAny * addChildPos_Widget (iWidget *, iAnyObject *child, enum iWidgetAddPos addPos);
+iAny * addChildFlags_Widget (iWidget *, iAnyObject child, int64_t childFlags); / holds a ref */
+iAny * insertChildAfter_Widget (iWidget *, iAnyObject *child, size_t afterIndex);
+iAny * insertChildAfterFlags_Widget(iWidget *, iAnyObject *child, size_t afterIndex, int64_t childFlags);
iAny * removeChild_Widget (iWidget *, iAnyObject child); / returns a ref */
iAny * child_Widget (iWidget , size_t index); / O(n) */
size_t childIndex_Widget (const iWidget *, const iAnyObject child); / O(n) */
diff --git a/src/ui/window.c b/src/ui/window.c
index 3958f682..09238b2a 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -33,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "paint.h"
#include "util.h"
#include "keys.h"
+#include "touch.h"
#include "../app.h"
#include "../visited.h"
#include "../gmcerts.h"
@@ -41,18 +42,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#if defined (iPlatformMsys)
#endif
-#if defined (iPlatformApple) && !defined (iPlatformIOS)
+#if defined (iPlatformAppleDesktop)
#endif
+#if defined (iPlatformAppleMobile)
+# include "ios.h"
+#endif
#include <the_Foundation/file.h>
#include <the_Foundation/path.h>
+#include <the_Foundation/regexp.h>
#include <SDL_hints.h>
#include <SDL_timer.h>
#include <SDL_syswm.h>
#define STB_IMAGE_IMPLEMENTATION
+#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image.h"
+#include "stb_image_resize.h"
static iWindow *theWindow_ = NULL;
@@ -84,6 +91,59 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
}
else if (equal_Command(cmd, "window.focus.lost")) {
setFocus_Widget(NULL);
setTextColor_LabelWidget(findWidget_App("winbar.app"), uiAnnotation_ColorId);
setTextColor_LabelWidget(findWidget_App("winbar.title"), uiAnnotation_ColorId);
return iFalse;
setTextColor_LabelWidget(findWidget_App("winbar.app"), uiTextAppTitle_ColorId);
setTextColor_LabelWidget(findWidget_App("winbar.title"), uiTextStrong_ColorId);
return iFalse;
const int snap = argLabel_Command(cmd, "snap");
if (snap) {
iWindow *window = get_Window();
iInt2 coord = coord_Command(cmd);
iInt2 size = init_I2(argLabel_Command(cmd, "width"),
argLabel_Command(cmd, "height"));
SDL_SetWindowPosition(window->win, coord.x, coord.y);
SDL_SetWindowSize(window->win, size.x, size.y);
window->place.snap = snap;
return iTrue;
}
setSnap_Window(get_Window(), none_WindowSnap);
return iTrue;
SDL_MinimizeWindow(get_Window()->win);
return iTrue;
SDL_PushEvent(&(SDL_Event){ .type = SDL_QUIT });
return iTrue;
/* Place the sidebar next to or under doctabs depending on orientation. */
iSidebarWidget *sidebar = findChild_Widget(root, "sidebar");
removeChild_Widget(parent_Widget(sidebar), sidebar);
setButtonFont_SidebarWidget(sidebar, isLandscape_App() ? uiLabel_FontId : uiLabelLarge_FontId);
setBackgroundColor_Widget(findChild_Widget(as_Widget(sidebar), "buttons"),
isPortrait_App() ? uiBackgroundUnfocusedSelection_ColorId
: uiBackground_ColorId);
if (isLandscape_App()) {
addChildPos_Widget(findChild_Widget(root, "tabs.content"), iClob(sidebar), front_WidgetAddPos);
setWidth_SidebarWidget(sidebar, 73 * gap_UI);
if (isVisible_Widget(findWidget_App("sidebar2"))) {
postCommand_App("sidebar2.toggle");
}
}
else {
addChildPos_Widget(findChild_Widget(root, "stack"), iClob(sidebar), back_WidgetAddPos);
setWidth_SidebarWidget(sidebar, width_Widget(root));
}
return iFalse;
}
else if (handleCommand_App(cmd)) {
@@ -92,11 +152,12 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
return iFalse;
}
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
#endif
#if !defined (iHaveNativeMenus)
+#if !defined (iPlatformAppleMobile)
/* TODO: Submenus wouldn't hurt here. */
static const iMenuItem navMenuItems_[] = {
{ "New Tab", 't', KMOD_PRIMARY, "tabs.new" },
@@ -123,6 +184,48 @@ static const iMenuItem navMenuItems_[] = {
{ "---", 0, 0, NULL },
{ "Quit Lagrange", 'q', KMOD_PRIMARY, "quit" }
};
+#else
+/* Tablet menu. */
+static const iMenuItem tabletNavMenuItems_[] = {
+};
+/* Phone menu. */
+static const iMenuItem phoneNavMenuItems_[] = {
+};
+#endif /* AppleMobile */
#endif
#if defined (iHaveNativeMenus)
@@ -190,20 +293,31 @@ static const iMenuItem helpMenuItems_[] = {
};
#endif
+#if defined (iPlatformAppleMobile)
static const iMenuItem identityButtonMenuItems_[] = {
{ "No Active Identity", 0, 0, "ident.showactive" },
{ "---", 0, 0, NULL },
-#if !defined (iHaveNativeMenus)
+};
+#else /* desktop */
+static const iMenuItem identityButtonMenuItems_[] = {
+# if !defined (iHaveNativeMenus)
{ "New Identity...", SDLK_n, KMOD_PRIMARY | KMOD_SHIFT, "ident.new" },
{ "Import...", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" },
{ "---", 0, 0, NULL },
{ "Show Identities", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" },
-#else
+# else
{ "New Identity...", 0, 0, "ident.new" },
{ "---", 0, 0, NULL },
{ "Show Identities", 0, 0, "sidebar.mode arg:3 show:1" },
#endif
};
+#endif
static const char *reloadCStr_ = "\U0001f503";
@@ -291,7 +405,9 @@ static void updateNavBarIdentity_(iWidget *navBar) {
const iGmIdentity *ident =
identityForUrl_GmCerts(certs_App(), url_DocumentWidget(document_App()));
iWidget *button = findChild_Widget(navBar, "navbar.ident");
setFlags_Widget(button, selected_WidgetFlag, ident != NULL);
/* Update menu. */
iLabelWidget *idItem = child_Widget(findChild_Widget(button, "menu"), 0);
setTextCStr_LabelWidget(
@@ -317,8 +433,8 @@ static uint32_t updateReloadAnimation_Window_(uint32_t interval, void *window) {
}
static void setReloadLabel_Window_(iWindow *d, iBool animating) {
animating ? loadAnimationCStr_() : reloadCStr_);
}
static void checkLoadAnimation_Window_(iWindow *d) {
@@ -333,22 +449,113 @@ static void checkLoadAnimation_Window_(iWindow *d) {
setReloadLabel_Window_(d, isOngoing);
}
+static void updatePadding_Window_(iWindow *d) {
+#if defined (iPlatformAppleMobile)
float left, top, right, bottom;
safeAreaInsets_iOS(&left, &top, &right, &bottom);
setPadding_Widget(findChild_Widget(d->root, "navdiv"), left, top, right, 0);
iWidget *toolBar = findChild_Widget(d->root, "toolbar");
if (toolBar) {
setPadding_Widget(toolBar, left, 0, right, bottom);
}
+#endif
+}
+static void dismissPortraitPhoneSidebars_(void) {
iWidget *sidebar = findWidget_App("sidebar");
iWidget *sidebar2 = findWidget_App("sidebar2");
if (isVisible_Widget(sidebar)) {
postCommand_App("sidebar.toggle");
setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag);
}
if (isVisible_Widget(sidebar2)) {
postCommand_App("sidebar2.toggle");
setVisualOffset_Widget(sidebar2, height_Widget(sidebar2), 250, easeIn_AnimFlag);
}
+// setFlags_Widget(findWidget_App("toolbar.ident"), noBackground_WidgetFlag, iTrue);
+// setFlags_Widget(findWidget_App("toolbar.view"), noBackground_WidgetFlag, iTrue);
+}
+static iBool willPerformSearchQuery_(const iString *userInput) {
return iFalse;
+}
+static void showSearchQueryIndicator_(iBool show) {
(iInputWidget *) parent_Widget(indicator), -1, show ? width_Widget(indicator) : 0);
+}
+static int navBarAvailableSpace_(iWidget *navBar) {
const iWidget *child = i.object;
if (~flags_Widget(child) & expand_WidgetFlag &&
isVisible_Widget(child) &&
cmp_String(id_Widget(child), "url")) {
avail -= width_Widget(child);
}
+}
static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
if (equal_Command(cmd, "window.resized")) {
const iBool isNarrow = width_Rect(bounds_Widget(navBar)) / gap_UI < 140;
const iBool isPhone = deviceType_App() == phone_AppDeviceType;
const iBool isNarrow = !isPhone && width_Rect(bounds_Widget(navBar)) / gap_UI < 140;
/* Adjust navbar padding. */ {
int hPad = isPhone || isNarrow ? gap_UI / 2 : gap_UI * 3 / 2;
int vPad = gap_UI * 3 / 2;
int topPad = !findWidget_App("winbar") ? gap_UI / 2 : 0;
setPadding_Widget(navBar, hPad, vPad / 2 + topPad, hPad, vPad / 2);
}
/* Button sizing. */
if (isNarrow ^ ((flags_Widget(navBar) & tight_WidgetFlag) != 0)) {
setFlags_Widget(navBar, tight_WidgetFlag, isNarrow);
iForEach(ObjectList, i, navBar->children) {
iWidget *child = as_Widget(i.object);
setFlags_Widget(
child, tight_WidgetFlag, isNarrow || !cmp_String(id_Widget(child), "lock"));
child, tight_WidgetFlag, isNarrow);
if (isInstance_Object(i.object, &Class_LabelWidget)) {
iLabelWidget *label = i.object;
updateSize_LabelWidget(label);
}
}
/* Note that InputWidget uses the `tight` flag to adjust its inner padding. */
setContentPadding_InputWidget(findChild_Widget(navBar, "url"),
width_Widget(findChild_Widget(navBar, "navbar.lock")) * 0.75,
-1);
}
if (isPhone) {
static const char *buttons[] = {
"navbar.back", "navbar.forward", "navbar.ident", "navbar.home", "navbar.menu"
};
setFlags_Widget(findWidget_App("toolbar"), hidden_WidgetFlag, isLandscape_App());
iForIndices(i, buttons) {
iLabelWidget *btn = findChild_Widget(navBar, buttons[i]);
setFlags_Widget(as_Widget(btn), hidden_WidgetFlag, isPortrait_App());
if (isLandscape_App()) {
/* Collapsing sets size to zero and the label doesn't know when to update
its own size automatically. */
updateSize_LabelWidget(btn);
}
}
arrange_Widget(get_Window()->root);
}
/* Resize the URL input field. */ {
iWidget *urlBar = findChild_Widget(navBar, "url");
urlBar->rect.size.x = iMini(navBarAvailableSpace_(navBar), 167 * gap_UI);
arrange_Widget(navBar);
}
arrange_Widget(navBar);
refresh_Widget(navBar);
postCommand_Widget(navBar, "layout.changed id:navbar");
return iFalse;
@@ -368,14 +575,18 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
return iTrue;
}
else if (equal_Command(cmd, "input.edited")) {
iAnyObject *url = findChild_Widget(navBar, "url");
iAnyObject * url = findChild_Widget(navBar, "url");
const iString *text = text_InputWidget(url);
const iBool show = willPerformSearchQuery_(text);
showSearchQueryIndicator_(show);
if (pointer_Command(cmd) == url) {
submit_LookupWidget(findWidget_App("lookup"), text_InputWidget(url));
submit_LookupWidget(findWidget_App("lookup"), text);
return iTrue;
}
}
else if (startsWith_CStr(cmd, "input.ended id:url ")) {
iInputWidget *url = findChild_Widget(navBar, "url");
showSearchQueryIndicator_(iFalse);
if (isEmpty_String(text_InputWidget(url))) {
/* User entered nothing; restore the current URL. */
setText_InputWidget(url, url_DocumentWidget(document_App()));
@@ -385,9 +596,14 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
!isFocused_Widget(findWidget_App("lookup"))) {
iString *newUrl = copy_String(text_InputWidget(url));
trim_String(newUrl);
postCommandf_App(
"open url:%s",
cstr_String(absoluteUrl_String(&iStringLiteral(""), collect_String(newUrl))));
if (willPerformSearchQuery_(newUrl)) {
postCommandf_App("open url:%s", cstr_String(searchQueryUrl_App(newUrl)));
}
else {
postCommandf_App(
"open url:%s",
cstr_String(absoluteUrl_String(&iStringLiteral(""), collect_String(newUrl))));
}
return iTrue;
}
}
@@ -402,12 +618,11 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
postCommand_App("visited.changed"); /* sidebar will update */
setText_InputWidget(url, urlStr);
checkLoadAnimation_Window_(get_Window());
dismissPortraitPhoneSidebars_();
updateNavBarIdentity_(navBar);
/* Icon updates should be limited to automatically chosen icons if the user
is allowed to pick their own in the future. */
if (updateBookmarkIcon_Bookmarks(
bookmarks_App(),
urlStr,
if (updateBookmarkIcon_Bookmarks(bookmarks_App(), urlStr,
siteIcon_GmDocument(document_DocumentWidget(document_App())))) {
postCommand_App("bookmarks.changed");
}
@@ -421,6 +636,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
iInputWidget *url = findChild_Widget(navBar, "url");
setTextCStr_InputWidget(url, suffixPtr_Command(cmd, "url"));
checkLoadAnimation_Window_(get_Window());
dismissPortraitPhoneSidebars_();
return iFalse;
}
}
@@ -503,7 +719,97 @@ static iBool handleSearchBarCommands_(iWidget *searchBar, const char *cmd) {
return iFalse;
}
+#if defined (iPlatformAppleMobile)
+static void dismissSidebar_(iWidget *sidebar, const char *toolButtonId) {
postCommandf_App("%s.toggle", cstr_String(id_Widget(sidebar)));
if (toolButtonId) {
+// setFlags_Widget(findWidget_App(toolButtonId), noBackground_WidgetFlag, iTrue);
}
setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag);
+}
+static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) {
argLabel_Command(cmd, "button") == SDL_BUTTON_RIGHT) {
iWidget *menu = findChild_Widget(toolBar, "toolbar.menu");
arrange_Widget(menu);
openMenu_Widget(menu, init_I2(0, -height_Widget(menu)));
return iTrue;
/* TODO: Clean this up. */
iWidget *sidebar = findWidget_App("sidebar");
iWidget *sidebar2 = findWidget_App("sidebar2");
dismissSidebar_(sidebar2, "toolbar.ident");
const iBool isVisible = isVisible_Widget(sidebar);
+// setFlags_Widget(findChild_Widget(toolBar, "toolbar.view"), noBackground_WidgetFlag,
+// isVisible);
/* If a sidebar hasn't been shown yet, it's height is zero. */
const int viewHeight = rootSize_Window(get_Window()).y;
if (arg_Command(cmd) >= 0) {
postCommandf_App("sidebar.mode arg:%d show:1", arg_Command(cmd));
if (!isVisible) {
setVisualOffset_Widget(sidebar, viewHeight, 0, 0);
setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag);
}
}
else {
postCommandf_App("sidebar.toggle");
if (isVisible) {
setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag);
}
else {
setVisualOffset_Widget(sidebar, viewHeight, 0, 0);
setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag);
}
}
return iTrue;
/* TODO: Clean this up. */
iWidget *sidebar = findWidget_App("sidebar");
iWidget *sidebar2 = findWidget_App("sidebar2");
dismissSidebar_(sidebar, "toolbar.view");
const iBool isVisible = isVisible_Widget(sidebar2);
+// setFlags_Widget(findChild_Widget(toolBar, "toolbar.ident"), noBackground_WidgetFlag,
+// isVisible);
/* If a sidebar hasn't been shown yet, it's height is zero. */
const int viewHeight = rootSize_Window(get_Window()).y;
if (isVisible) {
dismissSidebar_(sidebar2, NULL);
}
else {
postCommand_App("sidebar2.mode arg:3 show:1");
int offset = height_Widget(sidebar2);
if (offset == 0) offset = rootSize_Window(get_Window()).y;
setVisualOffset_Widget(sidebar2, offset, 0, 0);
setVisualOffset_Widget(sidebar2, 0, 400, easeOut_AnimFlag | softer_AnimFlag);
}
return iTrue;
iLabelWidget *viewTool = findChild_Widget(toolBar, "toolbar.view");
updateTextCStr_LabelWidget(viewTool, icon_SidebarMode(arg_Command(cmd)));
return iFalse;
+}
+#endif /* defined (iPlatformAppleMobile) */
+static iLabelWidget *newLargeIcon_LabelWidget(const char *text, const char *cmd) {
+}
+static int appIconSize_(void) {
+}
static void setupUserInterface_Window(iWindow *d) {
/* Children of root cover the entire window. */
setFlags_Widget(d->root, resizeChildren_WidgetFlag, iTrue);
setCommandHandler_Widget(d->root, handleRootCommands_);
@@ -512,32 +818,79 @@ static void setupUserInterface_Window(iWindow *d) {
setId_Widget(div, "navdiv");
addChild_Widget(d->root, iClob(div));
+#if defined (LAGRANGE_CUSTOM_FRAME)
setPadding1_Widget(div, 1);
iWidget *winBar = new_Widget();
setPadding_Widget(winBar, 0, gap_UI / 3, 0, 0);
setId_Widget(winBar, "winbar");
setFlags_Widget(winBar,
arrangeHeight_WidgetFlag | resizeChildren_WidgetFlag |
arrangeHorizontal_WidgetFlag | collapse_WidgetFlag,
iTrue);
iWidget *appIcon;
setId_Widget(
addChild_Widget(winBar, iClob(appIcon = makePadding_Widget(0))), "winbar.icon");
iLabelWidget *appButton =
addChildFlags_Widget(winBar,
iClob(new_LabelWidget("Lagrange", NULL)),
fixedHeight_WidgetFlag | frameless_WidgetFlag);
setTextColor_LabelWidget(appButton, uiTextAppTitle_ColorId);
setId_Widget(as_Widget(appButton), "winbar.app");
iLabelWidget *appTitle;
setFont_LabelWidget(appButton, uiContentBold_FontId);
setId_Widget(addChildFlags_Widget(winBar,
iClob(appTitle = new_LabelWidget("", NULL)),
expand_WidgetFlag | fixedHeight_WidgetFlag |
frameless_WidgetFlag | commandOnClick_WidgetFlag),
"winbar.title");
setTextColor_LabelWidget(appTitle, uiTextStrong_ColorId);
iLabelWidget *appMin, *appMax, *appClose;
setId_Widget(addChildFlags_Widget(
winBar,
iClob(appMin = newLargeIcon_LabelWidget("\u2013", "window.minimize")),
frameless_WidgetFlag),
"winbar.min");
setSize_Widget(as_Widget(appMin),
init_I2(gap_UI * 11.5f, height_Widget(appTitle)));
addChildFlags_Widget(
winBar,
iClob(appMax = newLargeIcon_LabelWidget("\u25a1", "window.maximize toggle:1")),
frameless_WidgetFlag);
setId_Widget(as_Widget(appMax), "winbar.max");
addChildFlags_Widget(winBar,
iClob(appClose = newLargeIcon_LabelWidget("\u2a2f", "window.close")),
frameless_WidgetFlag);
setFont_LabelWidget(appClose, uiContent_FontId);
setSize_Widget(as_Widget(appMax), as_Widget(appMin)->rect.size);
setSize_Widget(as_Widget(appClose), as_Widget(appMin)->rect.size);
setSize_Widget(appIcon, init_I2(appIconSize_(), as_Widget(appMin)->rect.size.y));
addChild_Widget(div, iClob(winBar));
setBackgroundColor_Widget(winBar, uiBackground_ColorId);
+#endif
/* Navigation bar. */ {
iWidget *navBar = new_Widget();
setId_Widget(navBar, "navbar");
/*setPadding_Widget(navBar, gap_UI / 2, 0, gap_UI / 2, 0);*/
setPadding_Widget(navBar, gap_UI, gap_UI / 2, gap_UI, gap_UI / 2);
setFlags_Widget(navBar,
arrangeHeight_WidgetFlag | resizeChildren_WidgetFlag |
arrangeHorizontal_WidgetFlag,
arrangeHeight_WidgetFlag |
resizeChildren_WidgetFlag |
arrangeHorizontal_WidgetFlag |
drawBackgroundToHorizontalSafeArea_WidgetFlag |
drawBackgroundToVerticalSafeArea_WidgetFlag,
iTrue);
addChild_Widget(div, iClob(navBar));
setBackgroundColor_Widget(navBar, uiBackground_ColorId);
setCommandHandler_Widget(navBar, handleNavBarCommands_);
addChild_Widget(navBar, iClob(newIcon_LabelWidget("\U0001f870", 0, 0, "navigate.back")));
addChild_Widget(navBar, iClob(newIcon_LabelWidget("\U0001f872", 0, 0, "navigate.forward")));
setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget("\U0001f870", 0, 0, "navigate.back")), collapse_WidgetFlag), "navbar.back");
setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget("\U0001f872", 0, 0, "navigate.forward")), collapse_WidgetFlag), "navbar.forward");
addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag);
iLabelWidget *idMenu = makeMenuButton_LabelWidget(
"\U0001f464", identityButtonMenuItems_, iElemCount(identityButtonMenuItems_));
setAlignVisually_LabelWidget(idMenu, iTrue);
addChild_Widget(navBar, iClob(idMenu));
setId_Widget(as_Widget(idMenu), "navbar.ident");
iLabelWidget *lock =
addChildFlags_Widget(navBar,
iClob(newIcon_LabelWidget("\U0001f513", SDLK_i, KMOD_PRIMARY, "document.info")),
frameless_WidgetFlag | tight_WidgetFlag);
setId_Widget(as_Widget(lock), "navbar.lock");
setFont_LabelWidget(lock, defaultSymbols_FontId);
updateTextCStr_LabelWidget(lock, "\U0001f512");
setId_Widget(addChildFlags_Widget(navBar, iClob(idMenu), collapse_WidgetFlag), "navbar.ident");
/* URL input field. */ {
iInputWidget *url = new_InputWidget(0);
setSelectAllOnFocus_InputWidget(url, iTrue);
@@ -545,8 +898,19 @@ static void setupUserInterface_Window(iWindow *d) {
setUrlContent_InputWidget(url, iTrue);
setNotifyEdits_InputWidget(url, iTrue);
setTextCStr_InputWidget(url, "gemini://");
addChildFlags_Widget(navBar, iClob(url), expand_WidgetFlag);
setPadding_Widget(as_Widget(url),0, 0, gap_UI * 1, 0);
addChildFlags_Widget(navBar, iClob(url), 0);
setPadding_Widget(as_Widget(url), 0, 0, gap_UI, 0);
/* Page information/certificate warning. */ {
iLabelWidget *lock =
addChildFlags_Widget(as_Widget(url),
iClob(newIcon_LabelWidget("\U0001f513", SDLK_i, KMOD_PRIMARY, "document.info")),
noBackground_WidgetFlag | frameless_WidgetFlag | moveToParentLeftEdge_WidgetFlag |
(deviceType_App() == desktop_AppDeviceType ? tight_WidgetFlag : 0));
setId_Widget(as_Widget(lock), "navbar.lock");
setFont_LabelWidget(lock, defaultSymbols_FontId);
updateTextCStr_LabelWidget(lock, "\U0001f512");
setContentPadding_InputWidget(url, width_Widget(lock) * 0.75, -1);
}
/* Feeds refresh indicator is inside the input field. */ {
iLabelWidget *fprog = new_LabelWidget(uiTextCaution_ColorEscape
"\u2605 Refreshing Feeds...", NULL);
@@ -569,18 +933,39 @@ static void setupUserInterface_Window(iWindow *d) {
iClob(progress),
moveToParentRightEdge_WidgetFlag);
}
/* Feeds refresh indicator is inside the input field. */ {
iLabelWidget *queryInd =
new_LabelWidget(uiTextAction_ColorEscape "\u21d2 Search Query", NULL);
setId_Widget(as_Widget(queryInd), "input.indicator.search");
setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId);
setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId);
setAlignVisually_LabelWidget(queryInd, iTrue);
shrink_Rect(&as_Widget(queryInd)->rect, init_I2(0, gap_UI));
addChildFlags_Widget(as_Widget(url),
iClob(queryInd),
moveToParentRightEdge_WidgetFlag | hidden_WidgetFlag);
}
}
setId_Widget(addChild_Widget(
navBar, iClob(newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"))),
"reload");
addChild_Widget(navBar,
addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag);
setId_Widget(addChildFlags_Widget(navBar,
iClob(newIcon_LabelWidget(
"\U0001f3e0", SDLK_h, KMOD_PRIMARY | KMOD_SHIFT, "navigate.home")));
"\U0001f3e0", SDLK_h, KMOD_PRIMARY | KMOD_SHIFT, "navigate.home")),
collapse_WidgetFlag),
"navbar.home");
#if !defined (iHaveNativeMenus)
+# if defined (iPlatformAppleMobile)
iLabelWidget *navMenu =
makeMenuButton_LabelWidget("\U0001d362", isPhone ? phoneNavMenuItems_ : tabletNavMenuItems_,
isPhone ? iElemCount(phoneNavMenuItems_) : iElemCount(tabletNavMenuItems_));
+# else
iLabelWidget *navMenu =
makeMenuButton_LabelWidget("\U0001d362", navMenuItems_, iElemCount(navMenuItems_));
+# endif
setAlignVisually_LabelWidget(navMenu, iTrue);
addChild_Widget(navBar, iClob(navMenu));
setId_Widget(addChildFlags_Widget(navBar, iClob(navMenu), collapse_WidgetFlag), "navbar.menu");
#else
insertMenuItems_MacOS("File", 1, fileMenuItems_, iElemCount(fileMenuItems_));
insertMenuItems_MacOS("Edit", 2, editMenuItems_, iElemCount(editMenuItems_));
@@ -591,13 +976,20 @@ static void setupUserInterface_Window(iWindow *d) {
#endif
}
/* Tab bar. */ {
iWidget *tabBar = makeTabs_Widget(div);
iWidget *mainStack = new_Widget();
setId_Widget(mainStack, "stack");
addChildFlags_Widget(div, iClob(mainStack), resizeChildren_WidgetFlag | expand_WidgetFlag |
unhittable_WidgetFlag);
iWidget *tabBar = makeTabs_Widget(mainStack);
setId_Widget(tabBar, "doctabs");
setFlags_Widget(tabBar, expand_WidgetFlag, iTrue);
setBackgroundColor_Widget(tabBar, uiBackground_ColorId);
appendTabPage_Widget(tabBar, iClob(new_DocumentWidget()), "Document", 0, 0);
iWidget *buttons = findChild_Widget(tabBar, "tabs.buttons");
setFlags_Widget(buttons, collapse_WidgetFlag | hidden_WidgetFlag, iTrue);
setFlags_Widget(buttons, collapse_WidgetFlag | hidden_WidgetFlag |
drawBackgroundToHorizontalSafeArea_WidgetFlag, iTrue);
if (deviceType_App() == phone_AppDeviceType) {
setBackgroundColor_Widget(buttons, uiBackground_ColorId);
}
setId_Widget(
addChild_Widget(buttons, iClob(newIcon_LabelWidget("\u2795", 0, 0, "tabs.new"))),
"newtab");
@@ -607,7 +999,14 @@ static void setupUserInterface_Window(iWindow *d) {
iSidebarWidget *sidebar1 = new_SidebarWidget(left_SideBarSide);
addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos);
iSidebarWidget *sidebar2 = new_SidebarWidget(right_SideBarSide);
addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos);
if (deviceType_App() != phone_AppDeviceType) {
addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos);
}
else {
/* The identities sidebar is always in the main area. */
addChild_Widget(findChild_Widget(d->root, "stack"), iClob(sidebar2));
setFlags_Widget(as_Widget(sidebar2), hidden_WidgetFlag, iTrue);
}
}
/* Lookup results. */ {
iLookupWidget *lookup = new_LookupWidget();
@@ -635,6 +1034,43 @@ static void setupUserInterface_Window(iWindow *d) {
addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9d ", 'g', KMOD_PRIMARY | KMOD_SHIFT, "find.prev")));
addChild_Widget(searchBar, iClob(newIcon_LabelWidget("\u2a2f", SDLK_ESCAPE, 0, "find.close")));
}
+#if defined (iPlatformAppleMobile)
iWidget *toolBar = new_Widget();
addChild_Widget(div, iClob(toolBar));
setId_Widget(toolBar, "toolbar");
setCommandHandler_Widget(toolBar, handleToolBarCommands_);
setFlags_Widget(toolBar, collapse_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue);
setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId);
addChildFlags_Widget(toolBar, iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), frameless_WidgetFlag);
addChildFlags_Widget(toolBar, iClob(newLargeIcon_LabelWidget("\U0001f872", "navigate.forward")), frameless_WidgetFlag);
setId_Widget(addChildFlags_Widget(toolBar, iClob(newLargeIcon_LabelWidget("\U0001f464", "toolbar.showident")), frameless_WidgetFlag), "toolbar.ident");
setId_Widget(addChildFlags_Widget(toolBar, iClob(newLargeIcon_LabelWidget("\U0001f588", "toolbar.showview arg:-1")),
frameless_WidgetFlag | commandOnClick_WidgetFlag), "toolbar.view");
iLabelWidget *menuButton = makeMenuButton_LabelWidget("\U0001d362", phoneNavMenuItems_,
iElemCount(phoneNavMenuItems_));
setFont_LabelWidget(menuButton, uiLabelLarge_FontId);
addChildFlags_Widget(toolBar, iClob(menuButton), frameless_WidgetFlag);
iForEach(ObjectList, i, children_Widget(toolBar)) {
iLabelWidget *btn = i.object;
setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue);
setTextColor_LabelWidget(i.object, tmBannerIcon_ColorId);
+// setBackgroundColor_Widget(i.object, tmBannerSideTitle_ColorId);
}
const iMenuItem items[] = {
{ "Bookmarks", 0, 0, "toolbar.showview arg:0" },
{ "Feeds", 0, 0, "toolbar.showview arg:1" },
{ "History", 0, 0, "toolbar.showview arg:2" },
{ "Page Outline", 0, 0, "toolbar.showview arg:4" },
};
iWidget *menu = makeMenu_Widget(findChild_Widget(toolBar, "toolbar.view"),
items, iElemCount(items));
setId_Widget(menu, "toolbar.menu");
+#endif
iWidget *tabsMenu = makeMenu_Widget(d->root,
(iMenuItem[]){
{ "Close Tab", 0, 0, "tabs.close" },
@@ -667,8 +1103,8 @@ static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) {
const iInt2 oldSize = *size;
SDL_GetRendererOutputSize(d->render, &size->x, &size->y);
if (notifyAlways || !isEqual_I2(oldSize, *size)) {
const iBool isHoriz = (d->lastNotifiedSize.x != size->x);
const iBool isVert = (d->lastNotifiedSize.y != size->y);
const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x);
const iBool isVert = (d->place.lastNotifiedSize.y != size->y);
arrange_Widget(d->root);
postCommandf_App("window.resized width:%d height:%d horiz:%d vert:%d",
size->x,
@@ -676,7 +1112,7 @@ static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) {
isHoriz,
isVert);
postRefresh_App();
d->lastNotifiedSize = *size;
d->place.lastNotifiedSize = *size;
}
}
@@ -712,29 +1148,116 @@ static void drawBlank_Window_(iWindow *d) {
SDL_RenderPresent(d->render);
}
+#if defined (LAGRANGE_CUSTOM_FRAME)
+static SDL_HitTestResult hitTest_Window_(SDL_Window *win, const SDL_Point *pos, void *data) {
return SDL_HITTEST_NORMAL;
if (isLeft) {
return pos->y < captionHeight ? SDL_HITTEST_RESIZE_TOPLEFT
: pos->y > h - captionHeight ? SDL_HITTEST_RESIZE_BOTTOMLEFT
: (d->place.lastHit = SDL_HITTEST_RESIZE_LEFT);
}
if (isRight) {
return pos->y < captionHeight ? SDL_HITTEST_RESIZE_TOPRIGHT
: pos->y > h - captionHeight ? SDL_HITTEST_RESIZE_BOTTOMRIGHT
: (d->place.lastHit = SDL_HITTEST_RESIZE_RIGHT);
}
if (isTop) {
return pos->x < captionHeight ? SDL_HITTEST_RESIZE_TOPLEFT
: pos->x > w - captionHeight ? SDL_HITTEST_RESIZE_TOPRIGHT
: (d->place.lastHit = SDL_HITTEST_RESIZE_TOP);
}
if (isBottom) {
return pos->x < captionHeight ? SDL_HITTEST_RESIZE_BOTTOMLEFT
: pos->x > w - captionHeight ? SDL_HITTEST_RESIZE_BOTTOMRIGHT
: (d->place.lastHit = SDL_HITTEST_RESIZE_BOTTOM);
}
return SDL_HITTEST_DRAGGABLE;
+}
+SDL_HitTestResult hitTest_Window(const iWindow *d, iInt2 pos) {
+}
+#endif
iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) {
flags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN;
+#if defined (LAGRANGE_CUSTOM_FRAME)
/* We are drawing a custom frame so hide the default one. */
flags |= SDL_WINDOW_BORDERLESS;
+#endif
if (SDL_CreateWindowAndRenderer(
width_Rect(rect), height_Rect(rect), flags, &d->win, &d->render)) {
return iFalse;
}
+#if defined (LAGRANGE_CUSTOM_FRAME)
/* Register a handler for window hit testing (drag, resize). */
SDL_SetWindowHitTest(d->win, hitTest_Window_, d);
SDL_SetWindowResizable(d->win, SDL_TRUE);
+#endif
return iTrue;
}
+#if defined (iPlatformLinux) || defined (LAGRANGE_CUSTOM_FRAME)
+static SDL_Surface *loadAppIconSurface_(int resized) {
constData_Block(icon), size_Block(icon), &w, &h, &num, STBI_rgb_alpha);
stbi_uc * rsPixels = malloc(num * resized * resized);
stbir_resize_uint8(pixels, w, h, 0, rsPixels, resized, resized, 0, num);
free(pixels);
pixels = rsPixels;
w = h = resized;
pixels, w, h, 8 * num, w * num, SDL_PIXELFORMAT_RGBA32);
+}
+#endif
void init_Window(iWindow *d, iRect rect) {
theWindow_ = d;
d->win = NULL;
iZap(d->cursors);
d->pendingCursor = NULL;
d->isDrawFrozen = iTrue;
d->isMouseInside = iTrue;
d->focusGainedAt = 0;
uint32_t flags = 0;
-#if defined (iPlatformApple)
+#if defined (iPlatformAppleDesktop)
SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl");
+#elif defined (iPlatformAppleMobile)
#else
flags |= SDL_WINDOW_OPENGL;
#endif
@@ -751,7 +1274,7 @@ void init_Window(iWindow *d, iRect rect) {
if (left_Rect(rect) >= 0 || top_Rect(rect) >= 0) {
SDL_SetWindowPosition(d->win, left_Rect(rect), top_Rect(rect));
}
SDL_SetWindowMinimumSize(d->win, minSize.x, minSize.y);
SDL_SetWindowTitle(d->win, "Lagrange");
/* Some info. */ {
@@ -774,26 +1297,23 @@ void init_Window(iWindow *d, iRect rect) {
d->pixelRatio = pixelRatio_Window_(d);
setPixelRatio_Metrics(d->pixelRatio * d->uiScale);
#if defined (iPlatformMsys)
SDL_SetWindowMinimumSize(d->win, minSize.x * d->pixelRatio, minSize.y * d->pixelRatio);
useExecutableIconResource_SDLWindow(d->win);
#endif
#if defined (iPlatformLinux)
SDL_SetWindowMinimumSize(d->win, minSize.x * d->pixelRatio, minSize.y * d->pixelRatio);
/* Load the window icon. */ {
int w, h, num;
const iBlock *icon = &imageLagrange64_Embedded;
stbi_uc *pixels = stbi_load_from_memory(constData_Block(icon),
size_Block(icon),
&w,
&h,
&num,
STBI_rgb_alpha);
SDL_Surface *surf =
SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h, 32, 4 * w, SDL_PIXELFORMAT_RGBA32);
SDL_Surface *surf = loadAppIconSurface_(0);
SDL_SetWindowIcon(d->win, surf);
free(surf->pixels);
SDL_FreeSurface(surf);
stbi_image_free(pixels);
}
+#endif
+#if defined (iPlatformAppleMobile)
#endif
d->root = new_Widget();
setFlags_Widget(d->root, focusRoot_WidgetFlag, iTrue);
@@ -805,6 +1325,19 @@ void init_Window(iWindow *d, iRect rect) {
setupUserInterface_Window(d);
postCommand_App("bindings.changed"); /* update from bindings */
updateRootSize_Window_(d, iFalse);
+#if defined (LAGRANGE_CUSTOM_FRAME)
SDL_Surface *surf = loadAppIconSurface_(appIconSize_());
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
d->appIcon = SDL_CreateTextureFromSurface(d->render, surf);
free(surf->pixels);
SDL_FreeSurface(surf);
/* We need to observe non-client-area events. */
SDL_EventState(SDL_SYSWMEVENT, SDL_TRUE);
+#endif
}
void deinit_Window(iWindow *d) {
@@ -827,48 +1360,162 @@ SDL_Renderer *renderer_Window(const iWindow *d) {
return d->render;
}
-static iBool isMaximized_Window_(const iWindow *d) {
-#if !defined (iPlatformApple)
-#else
+iBool isFullscreen_Window(const iWindow *d) {
+}
+static void invalidate_Window_(iWindow *d) {
+}
+static iBool isNormalPlacement_Window_(const iWindow *d) {
+#if defined (iPlatformApple)
return iTrue;
+#endif
+}
+static iBool unsnap_Window_(iWindow *d, const iInt2 *newPos) {
+#if defined (LAGRANGE_CUSTOM_FRAME)
return iFalse;
if (!newPos || (d->place.lastHit == SDL_HITTEST_RESIZE_LEFT ||
d->place.lastHit == SDL_HITTEST_RESIZE_RIGHT)) {
return iFalse;
}
if (newPos) {
SDL_Rect usable;
SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable);
/* Snap to top. */
if (snap == yMaximized_WindowSnap &&
iAbs(newPos->y - usable.y) < lineHeight_Text(uiContent_FontId) * 2) {
setSnap_Window(d, redo_WindowSnap | yMaximized_WindowSnap);
return iFalse;
}
}
if (snap_Window(d) == yMaximized_WindowSnap && newPos) {
d->place.normalRect.pos = *newPos;
}
//printf("unsnap\n"); fflush(stdout);
setSnap_Window(d, none_WindowSnap);
return iTrue;
#endif
}
static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
switch (ev->event) {
case SDL_WINDOWEVENT_EXPOSED:
if (!d->isExposed) {
drawBlank_Window_(d); /* avoid showing system-provided contents */
d->isExposed = iTrue;
}
/* Since we are manually controlling when to redraw the window, we are responsible
for ensuring that window contents get redrawn after expose events. Under certain
circumstances (e.g., under openbox), not doing this would mean that the window
is missing contents until other events trigger a refresh. */
postRefresh_App();
#if defined (LAGRANGE_ENABLE_WINDOWPOS_FIX)
if (d->initialPos.x >= 0) {
if (d->place.initialPos.x >= 0) {
int bx, by;
SDL_GetWindowBordersSize(d->win, &by, &bx, NULL, NULL);
SDL_SetWindowPosition(d->win, d->initialPos.x + bx, d->initialPos.y + by);
d->initialPos = init1_I2(-1);
SDL_SetWindowPosition(d->win, d->place.initialPos.x + bx, d->place.initialPos.y + by);
d->place.initialPos = init1_I2(-1);
}
#endif
return iFalse;
case SDL_WINDOWEVENT_MOVED: {
if (!isMaximized_Window_(d) && !d->isDrawFrozen) {
d->lastRect.pos = init_I2(ev->data1, ev->data2);
if (d->isMinimized) {
return iFalse;
}
const iInt2 newPos = init_I2(ev->data1, ev->data2);
if (isEqual_I2(newPos, init1_I2(-32000))) { /* magic! */
/* Maybe minimized? Seems like a Windows constant of some kind. */
d->isMinimized = iTrue;
return iFalse;
}
+#if defined (LAGRANGE_CUSTOM_FRAME)
/* Set the snap position depending on where the mouse cursor is. */
if (prefs_App()->customFrame) {
SDL_Rect usable;
iInt2 mouse = cursor_Win32(); /* SDL is unaware of the current cursor pos */
SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable);
const iBool isTop = iAbs(mouse.y - usable.y) < gap_UI * 20;
const iBool isBottom = iAbs(usable.y + usable.h - mouse.y) < gap_UI * 20;
if (iAbs(mouse.x - usable.x) < gap_UI) {
setSnap_Window(d,
redo_WindowSnap | left_WindowSnap |
(isTop ? topBit_WindowSnap : 0) |
(isBottom ? bottomBit_WindowSnap : 0));
return iTrue;
}
if (iAbs(mouse.x - usable.x - usable.w) < gap_UI) {
setSnap_Window(d,
redo_WindowSnap | right_WindowSnap |
(isTop ? topBit_WindowSnap : 0) |
(isBottom ? bottomBit_WindowSnap : 0));
return iTrue;
}
if (iAbs(mouse.y - usable.y) < 2) {
setSnap_Window(d,
redo_WindowSnap | (d->place.lastHit == SDL_HITTEST_RESIZE_TOP
? yMaximized_WindowSnap
: maximized_WindowSnap));
return iTrue;
}
}
+#endif
//printf("MOVED: %d, %d\n", ev->data1, ev->data2); fflush(stdout);
if (unsnap_Window_(d, &newPos)) {
return iTrue;
}
if (isNormalPlacement_Window_(d)) {
d->place.normalRect.pos = newPos;
//printf("normal rect set (move)\n"); fflush(stdout);
iInt2 border = zero_I2();
#if !defined (iPlatformApple)
SDL_GetWindowBordersSize(d->win, &border.y, &border.x, NULL, NULL);
#endif
d->lastRect.pos = max_I2(zero_I2(), sub_I2(d->lastRect.pos, border));
d->place.normalRect.pos = max_I2(zero_I2(), sub_I2(d->place.normalRect.pos, border));
}
return iTrue;
}
case SDL_WINDOWEVENT_RESIZED:
if (!isMaximized_Window_(d) && !d->isDrawFrozen) {
d->lastRect.size = init_I2(ev->data1, ev->data2);
updatePadding_Window_(d);
if (d->isMinimized) {
updateRootSize_Window_(d, iTrue);
return iTrue;
}
if (unsnap_Window_(d, NULL)) {
return iTrue;
}
if (isNormalPlacement_Window_(d)) {
d->place.normalRect.size = init_I2(ev->data1, ev->data2);
//printf("normal rect set (resize)\n"); fflush(stdout);
}
updateRootSize_Window_(d, iTrue /* we were already redrawing during the resize */);
postRefresh_App();
return iTrue;
case SDL_WINDOWEVENT_RESTORED:
updateRootSize_Window_(d, iTrue);
invalidate_Window_(d);
d->isMinimized = iFalse;
postRefresh_App();
return iTrue;
case SDL_WINDOWEVENT_MINIMIZED:
d->isMinimized = iTrue;
return iTrue;
case SDL_WINDOWEVENT_LEAVE:
unhover_Widget();
@@ -881,6 +1528,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
return iTrue;
case SDL_WINDOWEVENT_TAKE_FOCUS:
SDL_SetWindowInputFocus(d->win);
postRefresh_App();
return iTrue;
case SDL_WINDOWEVENT_FOCUS_GAINED:
d->focusGainedAt = SDL_GetTicks();
@@ -904,13 +1552,22 @@ static void applyCursor_Window_(iWindow *d) {
iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
switch (ev->type) {
+#if defined (LAGRANGE_CUSTOM_FRAME)
case SDL_SYSWMEVENT: {
/* We observe native Win32 messages for better user interaction with the
window frame. Mouse clicks especially will not generate normal SDL
events if they happen on the custom hit-tested regions. These events
are processed only there; the UI widgets do not get involved. */
processNativeEvent_Win32(ev->syswm.msg, d);
break;
}
+#endif
case SDL_WINDOWEVENT: {
return handleWindowEvent_Window_(d, &ev->window);
}
case SDL_RENDER_TARGETS_RESET:
case SDL_RENDER_DEVICE_RESET: {
resetFonts_Text();
postCommand_App("theme.changed"); /* forces UI invalidation */
invalidate_Window_(d);
break;
}
default: {
@@ -927,12 +1584,19 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
postRefresh_App();
return iTrue;
}
if (processEvent_Touch(&event)) {
return iTrue;
}
if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->focusGainedAt < 10) {
/* Suspiciously close to when input focus was received. For example under openbox,
closing xterm with Ctrl+D will cause the keydown event to "spill" over to us.
As a workaround, ignore these events. */
return iTrue; /* won't go to bindings, either */
}
if (event.type == SDL_MOUSEBUTTONDOWN && d->ignoreClick) {
d->ignoreClick = iFalse;
return iTrue;
}
/* Map mouse pointer coordinate to our coordinate system. */
if (event.type == SDL_MOUSEMOTION) {
setCursor_Window(d, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */
@@ -985,15 +1649,42 @@ void draw_Window(iWindow *d) {
if (d->isDrawFrozen) {
return;
}
-//#if !defined (NDEBUG)
-// printf("draw %d\n", d->frameTime); fflush(stdout);
-//#endif
when the custom frame is being used. */ {
+#if defined (iPlatformAppleMobile)
const iColor back = get_Color(tmBackground_ColorId);
+#else
const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap &&
~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP
? uiAnnotation_ColorId
: uiSeparator_ColorId);
+#endif
SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255);
SDL_RenderClear(d->render);
/* Draw widgets. */
d->frameTime = SDL_GetTicks();
draw_Widget(d->root);
+#if defined (LAGRANGE_CUSTOM_FRAME)
const int size = appIconSize_();
const iRect rect = bounds_Widget(appIcon);
const iInt2 mid = mid_Rect(rect);
const iBool isLight = isLight_ColorTheme(colorTheme_App());
iColor iconColor = get_Color(gotFocus || isLight ? white_ColorId : cyan_ColorId);
SDL_SetTextureColorMod(d->appIcon, iconColor.r, iconColor.g, iconColor.b);
SDL_SetTextureAlphaMod(d->appIcon, gotFocus || !isLight ? 255 : 92);
SDL_RenderCopy(
d->render,
d->appIcon,
NULL,
&(SDL_Rect){ left_Rect(rect) + gap_UI * 1.25f, mid.y - size / 2, size, size });
+#endif
#if 0
/* Text cache debugging. */ {
SDL_Texture *cache = glyphCache_Text();
@@ -1013,6 +1704,10 @@ void resize_Window(iWindow *d, int w, int h) {
void setTitle_Window(iWindow *d, const iString *title) {
SDL_SetWindowTitle(d->win, cstr_String(title));
updateText_LabelWidget(bar, title);
}
void setUiScale_Window(iWindow *d, float uiScale) {
@@ -1048,7 +1743,7 @@ uint32_t id_Window(const iWindow *d) {
}
iInt2 rootSize_Window(const iWindow *d) {
}
iInt2 coord_Window(const iWindow *d, int x, int y) {
@@ -1081,3 +1776,104 @@ uint32_t frameTime_Window(const iWindow *d) {
iWindow *get_Window(void) {
return theWindow_;
}
+void setSnap_Window(iWindow *d, int snapMode) {
if (snapMode == maximized_WindowSnap) {
SDL_MaximizeWindow(d->win);
}
else if (snapMode == fullscreen_WindowSnap) {
SDL_SetWindowFullscreen(d->win, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
else {
if (snap_Window(d) == fullscreen_WindowSnap) {
SDL_SetWindowFullscreen(d->win, 0);
}
else {
SDL_RestoreWindow(d->win);
}
}
return;
+#if defined (LAGRANGE_CUSTOM_FRAME)
return;
SDL_SetWindowFullscreen(d->win, 0);
case none_WindowSnap:
newRect = intersect_Rect(d->place.normalRect,
init_Rect(usable.x, usable.y, usable.w, usable.h));
break;
case left_WindowSnap:
newRect = init_Rect(usable.x, usable.y, usable.w / 2, usable.h);
break;
case right_WindowSnap:
newRect =
init_Rect(usable.x + usable.w / 2, usable.y, usable.w - usable.w / 2, usable.h);
break;
case maximized_WindowSnap:
newRect = init_Rect(usable.x, usable.y, usable.w, usable.h);
break;
case yMaximized_WindowSnap:
newRect.pos.y = 0;
newRect.size.y = usable.h;
SDL_GetWindowSize(d->win, &newRect.size.x, NULL);
SDL_GetWindowPosition(d->win, &newRect.pos.x, NULL);
/* Snap the window to left/right edges, if close by. */
if (iAbs(right_Rect(newRect) - (usable.x + usable.w)) < snapDist) {
newRect.pos.x = usable.x + usable.w - width_Rect(newRect);
}
if (iAbs(newRect.pos.x - usable.x) < snapDist) {
newRect.pos.x = usable.x;
}
break;
case fullscreen_WindowSnap:
SDL_SetWindowFullscreen(d->win, SDL_WINDOW_FULLSCREEN_DESKTOP);
break;
newRect.size.y /= 2;
newRect.pos.y += newRect.size.y;
d->place.snap == maximized_WindowSnap ? "\u25a2" : "\u25a1");
SDL_SetWindowPosition(d->win, newRect.pos.x, newRect.pos.y);
SDL_SetWindowSize(d->win, newRect.size.x, newRect.size.y);
postCommand_App("window.resized");
arrange_Widget(d->root);
postRefresh_App();
+#endif /* defined (LAGRANGE_CUSTOM_FRAME) */
+}
+int snap_Window(const iWindow *d) {
const int flags = SDL_GetWindowFlags(d->win);
if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) {
return fullscreen_WindowSnap;
}
else if (flags & SDL_WINDOW_MAXIMIZED) {
return maximized_WindowSnap;
}
return none_WindowSnap;
+}
diff --git a/src/ui/window.h b/src/ui/window.h
index 8be8b88d..7cd29d4b 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -32,13 +32,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
iDeclareType(Window)
iDeclareTypeConstructionArgs(Window, iRect rect)
+enum iWindowSnap {
+};
+iDeclareType(WindowPlacement)
+/* Tracking of window placement. */
+struct Impl_WindowPlacement {
+};
struct Impl_Window {
SDL_Window * win;
iBool isDrawFrozen; /* avoids premature draws while restoring window state */
iBool isMouseInside;
uint32_t focusGainedAt;
SDL_Renderer *render;
iWidget * root;
@@ -46,6 +71,7 @@ struct Impl_Window {
float uiScale;
uint32_t frameTime;
double presentTime;
SDL_Cursor * cursors[SDL_NUM_SYSTEM_CURSORS];
SDL_Cursor * pendingCursor;
int loadAnimTimer;
@@ -59,6 +85,7 @@ void setTitle_Window (iWindow *, const iString *title);
void setUiScale_Window (iWindow *, float uiScale);
void setFreezeDraw_Window (iWindow *, iBool freezeDraw);
void setCursor_Window (iWindow *, int cursor);
+void setSnap_Window (iWindow *, int snapMode);
uint32_t id_Window (const iWindow *);
iInt2 rootSize_Window (const iWindow *);
@@ -67,5 +94,11 @@ iInt2 coord_Window (const iWindow *, int x, int y);
iInt2 mouseCoord_Window (const iWindow *);
uint32_t frameTime_Window (const iWindow *);
SDL_Renderer *renderer_Window (const iWindow *);
+int snap_Window (const iWindow *);
+iBool isFullscreen_Window (const iWindow *);
iWindow * get_Window (void);
+#if defined (LAGRANGE_CUSTOM_FRAME)
+SDL_HitTestResult hitTest_Window(const iWindow *d, iInt2 pos);
+#endif
diff --git a/src/win32.c b/src/win32.c
index 5eb2a0d5..01ec73bf 100644
--- a/src/win32.c
+++ b/src/win32.c
@@ -21,10 +21,14 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "win32.h"
+#include "ui/window.h"
+#include "app.h"
#include <SDL_syswm.h>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
+#include <windowsx.h>
+#include <dwmapi.h>
#include <d2d1.h>
void setDPIAware_Win32(void) {
@@ -59,3 +63,132 @@ void useExecutableIconResource_SDLWindow(SDL_Window *win) {
}
}
}
+#if defined (LAGRANGE_CUSTOM_FRAME)
+iInt2 cursor_Win32(void) {
+}
+void processNativeEvent_Win32(const struct SDL_SysWMmsg *msg, iWindow *window) {
case WM_ACTIVATE: {
//LONG style = GetWindowLong(hwnd, GWL_STYLE);
//SetWindowLog(hwnd, GWL_STYLE, style);
iZap(winDown_); /* may have hidden the up event */
break;
}
case WM_KEYDOWN: {
if (wp == VK_LWIN) {
//printf("lwin down\n"); fflush(stdout);
winDown_[0] = iTrue;
}
else if (wp == VK_RWIN) {
//printf("rwin down\n"); fflush(stdout);
winDown_[1] = iTrue;
}
break;
}
case WM_KEYUP: {
if (winDown_[0] || winDown_[1]) {
/* Emulate the default window snapping behavior. */
int snap = snap_Window(window);
if (wp == VK_LEFT) {
snap &= ~(topBit_WindowSnap | bottomBit_WindowSnap);
setSnap_Window(window,
snap == right_WindowSnap ? 0 : left_WindowSnap);
}
else if (wp == VK_RIGHT) {
snap &= ~(topBit_WindowSnap | bottomBit_WindowSnap);
setSnap_Window(window,
snap == left_WindowSnap ? 0 : right_WindowSnap);
}
else if (wp == VK_UP) {
if (~snap & topBit_WindowSnap) {
setSnap_Window(window,
snap & bottomBit_WindowSnap ? snap & ~bottomBit_WindowSnap
: snap == left_WindowSnap || snap == right_WindowSnap
? snap | topBit_WindowSnap
: maximized_WindowSnap);
}
else {
postCommand_App("window.maximize");
}
}
else if (wp == VK_DOWN) {
if (snap == 0 || snap & bottomBit_WindowSnap) {
postCommand_App("window.minimize");
}
else {
setSnap_Window(window,
snap == maximized_WindowSnap ? 0
: snap & topBit_WindowSnap ? snap & ~topBit_WindowSnap
: snap == left_WindowSnap || snap == right_WindowSnap
? snap | bottomBit_WindowSnap
: 0);
}
}
}
if (wp == VK_LWIN) {
winDown_[0] = iFalse;
}
if (wp == VK_RWIN) {
winDown_[1] = iFalse;
}
break;
}
case WM_NCLBUTTONDBLCLK: {
POINT point = { GET_X_LPARAM(msg->msg.win.lParam),
GET_Y_LPARAM(msg->msg.win.lParam) };
ScreenToClient(hwnd, &point);
iInt2 pos = init_I2(point.x, point.y);
switch (hitTest_Window(window, pos)) {
case SDL_HITTEST_DRAGGABLE:
window->ignoreClick = iTrue; /* avoid hitting something inside the window */
postCommandf_App("window.%s",
snap_Window(window) ? "restore" : "maximize toggle:1");
break;
case SDL_HITTEST_RESIZE_TOP:
case SDL_HITTEST_RESIZE_BOTTOM: {
window->ignoreClick = iTrue; /* avoid hitting something inside the window */
setSnap_Window(window, yMaximized_WindowSnap);
break;
}
}
//fflush(stdout);
break;
}
+#if 0
case WM_NCLBUTTONUP: {
POINT point = { GET_X_LPARAM(msg->msg.win.lParam),
GET_Y_LPARAM(msg->msg.win.lParam) };
printf("%d,%d\n", point.x, point.y); fflush(stdout);
ScreenToClient(hwnd, &point);
iInt2 pos = init_I2(point.x, point.y);
if (hitTest_Window(window, pos) == SDL_HITTEST_DRAGGABLE) {
printf("released draggable\n"); fflush(stdout);
}
break;
}
+#endif
+#if 0
/* SDL does not use WS_SYSMENU on the window, so we can't display the system menu.
However, the only useful function in the menu would be moving-via-keyboard,
but that doesn't work with a custom frame. We could show a custom system menu? */
case WM_NCRBUTTONUP: {
POINT point = { GET_X_LPARAM(msg->msg.win.lParam),
GET_Y_LPARAM(msg->msg.win.lParam) };
HMENU menu = GetSystemMenu(hwnd, FALSE);
printf("menu at %d,%d menu:%p\n", point.x, point.y, menu); fflush(stdout);
TrackPopupMenu(menu, TPM_RIGHTBUTTON, point.x, point.y, 0, hwnd, NULL);
break;
}
+#endif
+}
+#endif /* defined (LAGRANGE_CUSTOM_FRAME) */
diff --git a/src/win32.h b/src/win32.h
index b5bc0b45..27aa0539 100644
--- a/src/win32.h
+++ b/src/win32.h
@@ -21,8 +21,19 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#pragma once
+#include <the_Foundation/vec2.h>
#include <SDL_video.h>
+#include <SDL_syswm.h>
+iDeclareType(Window)
void setDPIAware_Win32(void);
float desktopDPI_Win32(void);
void useExecutableIconResource_SDLWindow(SDL_Window *win);
+#if defined (LAGRANGE_CUSTOM_FRAME)
+iInt2 cursor_Win32(void);
+void processNativeEvent_Win32(const struct SDL_SysWMmsg *msg, iWindow *window);
+void setup_SDLWindow(SDL_Window *);
+#endif
--
2.25.1
text/plain
This content has been proxied by September (ba2dc).