basalt-gtk 0.0.0-rc5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/native/CMakeLists.txt +16 -0
- package/native/gtk/ComponentRegistryGtk.cpp +20 -0
- package/native/gtk/FontRegistryFontconfig.cpp +10 -2
- package/native/gtk/GtkFocus.cpp +22 -1
- package/native/gtk/GtkFocus.h +3 -0
- package/native/gtk/GtkImageLoader.cpp +75 -4
- package/native/gtk/GtkImageLoader.h +38 -1
- package/native/gtk/GtkMountingManager.cpp +622 -18
- package/native/gtk/GtkMountingManager.h +34 -0
- package/native/gtk/GtkScrollView.cpp +212 -2
- package/native/gtk/GtkScrollView.h +48 -0
- package/native/gtk/GtkTextInput.cpp +347 -21
- package/native/gtk/GtkTextInput.h +67 -0
- package/native/gtk/GtkTextPeer.cpp +385 -11
- package/native/gtk/GtkTextPeer.h +88 -0
- package/native/gtk/GtkTouchDispatcher.cpp +207 -1
- package/native/gtk/GtkTouchDispatcher.h +47 -0
- package/native/gtk/PangoTextLayout.cpp +611 -82
- package/native/gtk/PangoTextLayout.h +54 -1
- package/native/gtk/PangoTextLayoutManager.cpp +35 -13
- package/native/gtk/PlatformServicesGtk.cpp +416 -51
- package/native/gtk/RnTextDashes.h +65 -0
- package/native/gtk/RnView.cpp +2583 -22
- package/native/gtk/RnView.h +510 -0
- package/native/gtk/main_gtk.cpp +276 -3
- package/native/tests/GtkPixels.h +192 -0
- package/native/tests/TestMainGtk.cpp +13 -0
- package/native/tests/test_accessibility.cpp +519 -0
- package/native/tests/test_animated_image.cpp +330 -0
- package/native/tests/test_focus.cpp +101 -0
- package/native/tests/test_fonts.cpp +247 -0
- package/native/tests/test_gtk_clipboard.cpp +117 -0
- package/native/tests/test_gtk_paint.cpp +1018 -0
- package/native/tests/test_hittest.cpp +107 -0
- package/native/tests/test_mounting.cpp +79 -0
- package/native/tests/test_scrollview.cpp +485 -7
- package/native/tests/test_selection.cpp +476 -0
- package/native/tests/test_text.cpp +1532 -0
- package/native/tests/test_textinput.cpp +725 -0
- package/native/tests/test_viewprops.cpp +1498 -0
- package/package.json +4 -3
package/native/CMakeLists.txt
CHANGED
|
@@ -323,19 +323,35 @@ if(TARGET basalt_core)
|
|
|
323
323
|
tests/test_mounting.cpp
|
|
324
324
|
tests/test_view.cpp
|
|
325
325
|
tests/test_text.cpp
|
|
326
|
+
tests/test_selection.cpp
|
|
327
|
+
# Fonts loaded at runtime: core/FontRegistry.h's GTK half, the Pango
|
|
328
|
+
# context a late registration has to reload, and the measurement cache it
|
|
329
|
+
# has to empty.
|
|
330
|
+
tests/test_fonts.cpp
|
|
326
331
|
tests/test_focus.cpp
|
|
327
332
|
tests/test_key_events_gtk.cpp
|
|
328
333
|
tests/test_hittest.cpp
|
|
329
334
|
tests/test_scrollview.cpp
|
|
330
335
|
tests/test_image.cpp
|
|
336
|
+
# Animated images: gdk-pixbuf's frames, paced by core's clamp. Real pixels,
|
|
337
|
+
# because a view that advanced its iterator and kept painting the first
|
|
338
|
+
# texture would pass on the frame index alone.
|
|
339
|
+
tests/test_animated_image.cpp
|
|
331
340
|
tests/test_accessibility.cpp
|
|
332
341
|
tests/test_viewprops.cpp
|
|
342
|
+
# What reached the pixels, asking what tests/test_win32_paint.cpp asks of
|
|
343
|
+
# Direct2D. No display and no window: see tests/GtkPixels.h.
|
|
344
|
+
tests/test_gtk_paint.cpp
|
|
333
345
|
tests/test_textinput.cpp
|
|
334
346
|
tests/test_titlebar.cpp
|
|
335
347
|
# The drag regions, asking what tests/test_win32_titlebar.cpp asks of
|
|
336
348
|
# Windows. Needs a shown window: it walks up from gtk_widget_pick.
|
|
337
349
|
tests/test_titlebar_drag.cpp
|
|
338
350
|
tests/test_version.cpp
|
|
351
|
+
# The clipboard seam and which thread it runs on, asking what
|
|
352
|
+
# tests/test_win32_clipboard.cpp asks of Windows. Uses the real clipboard and
|
|
353
|
+
# puts back what was there.
|
|
354
|
+
tests/test_gtk_clipboard.cpp
|
|
339
355
|
# Hit testing lives with the touch dispatcher, which the host builds; the
|
|
340
356
|
# tests need it too, and it pulls in nothing else.
|
|
341
357
|
gtk/GtkFocus.cpp
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
#include "ComponentRegistry.h"
|
|
5
5
|
#include "ExpoImageComponent.h"
|
|
6
|
+
#include "SafeAreaComponent.h"
|
|
7
|
+
#include "ScreensComponent.h"
|
|
6
8
|
|
|
7
9
|
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
|
|
8
10
|
#include <react/renderer/components/FBReactNativeSpec/ComponentDescriptors.h>
|
|
@@ -24,6 +26,24 @@ ComponentRegistryFactory getDefaultComponentRegistryFactory() {
|
|
|
24
26
|
static auto providerRegistry = []() {
|
|
25
27
|
auto registry = std::make_shared<ComponentDescriptorProviderRegistry>();
|
|
26
28
|
registry->add(concreteComponentDescriptorProvider<ViewComponentDescriptor>());
|
|
29
|
+
// react-native-safe-area-context's provider. Registered on every
|
|
30
|
+
// platform, like expo-image's above: the descriptor is ordinary Fabric
|
|
31
|
+
// C++ and an app that never mounts one pays a registry entry. Without
|
|
32
|
+
// it the provider renders its children never, not badly. See
|
|
33
|
+
// core/SafeAreaComponent.h.
|
|
34
|
+
registry->add(concreteComponentDescriptorProvider<SafeAreaProviderComponentDescriptor>());
|
|
35
|
+
// react-native-screens. RNSScreen carries activityState, which is what
|
|
36
|
+
// hides the screen behind the one in front; the rest are containers that
|
|
37
|
+
// only need their names known, since an unregistered name becomes
|
|
38
|
+
// UnimplementedNativeView and carries none of the layout props. See
|
|
39
|
+
// core/ScreensComponent.h.
|
|
40
|
+
registry->add(concreteComponentDescriptorProvider<RNSScreenComponentDescriptor>());
|
|
41
|
+
registry->add(concreteComponentDescriptorProvider<RNSScreenStackComponentDescriptor>());
|
|
42
|
+
registry->add(concreteComponentDescriptorProvider<RNSScreenContainerComponentDescriptor>());
|
|
43
|
+
registry->add(concreteComponentDescriptorProvider<RNSScreenNavigationContainerComponentDescriptor>());
|
|
44
|
+
registry->add(concreteComponentDescriptorProvider<RNSScreenContentWrapperComponentDescriptor>());
|
|
45
|
+
registry->add(concreteComponentDescriptorProvider<RNSScreenStackHeaderConfigComponentDescriptor>());
|
|
46
|
+
registry->add(concreteComponentDescriptorProvider<RNSScreenStackHeaderSubviewComponentDescriptor>());
|
|
27
47
|
// Text is three descriptors, and only one of them mounts. <Text> becomes
|
|
28
48
|
// a Text node, its string a RawText node, and the outermost <Text>
|
|
29
49
|
// becomes a Paragraph that folds the whole subtree into a single
|
|
@@ -82,8 +82,16 @@ bool registerFont(const std::string &name, const std::string &path) {
|
|
|
82
82
|
|
|
83
83
|
// Pango caches which fonts exist. Adding one to fontconfig behind its back
|
|
84
84
|
// leaves it looking at the old list, so the family would not be found until
|
|
85
|
-
// something else happened to invalidate it.
|
|
86
|
-
//
|
|
85
|
+
// something else happened to invalidate it.
|
|
86
|
+
//
|
|
87
|
+
// Only meaningful for a fontconfig backed map, which is not the only kind
|
|
88
|
+
// Pango has, and the other kind is reachable: on a Mac, Homebrew's Pango
|
|
89
|
+
// answers this with a `PangoCairoCoreTextFontMap`, which does not consult
|
|
90
|
+
// fontconfig at all. A font loaded at runtime is then registered here and
|
|
91
|
+
// invisible to the text engine. That is a property of running the GTK host on
|
|
92
|
+
// macOS, which is a comparison arrangement rather than a target, and
|
|
93
|
+
// tests/test_fonts.cpp stops at the mapping when it sees that map. docs/
|
|
94
|
+
// TESTING.md records it.
|
|
87
95
|
PangoFontMap *fontMap = pango_cairo_font_map_get_default();
|
|
88
96
|
if (PANGO_IS_FC_FONT_MAP(fontMap)) {
|
|
89
97
|
pango_fc_font_map_config_changed(PANGO_FC_FONT_MAP(fontMap));
|
package/native/gtk/GtkFocus.cpp
CHANGED
|
@@ -42,6 +42,13 @@ GtkFocusManager::GtkFocusManager(GtkMountingManager *mountingManager, RnView *su
|
|
|
42
42
|
g_object_add_weak_pointer(G_OBJECT(keyController_),
|
|
43
43
|
reinterpret_cast<gpointer *>(&keyController_));
|
|
44
44
|
|
|
45
|
+
// Weak for the reason the controller above is: the root can be destroyed
|
|
46
|
+
// before this manager is, and then every line of the destructor is reading
|
|
47
|
+
// freed memory. GObject nulls the pointer on finalise, which is the only way
|
|
48
|
+
// to know -- see the destructor.
|
|
49
|
+
g_object_add_weak_pointer(G_OBJECT(surfaceRoot_),
|
|
50
|
+
reinterpret_cast<gpointer *>(&surfaceRoot_));
|
|
51
|
+
|
|
45
52
|
// The root may or may not be in a window yet, depending on the order the host
|
|
46
53
|
// built things in, so both are handled rather than one being assumed.
|
|
47
54
|
attachToWindow();
|
|
@@ -53,8 +60,19 @@ GtkFocusManager::~GtkFocusManager() {
|
|
|
53
60
|
g_signal_handlers_disconnect_by_data(window_, this);
|
|
54
61
|
window_ = nullptr;
|
|
55
62
|
}
|
|
56
|
-
if
|
|
63
|
+
// Only if the root is still there, which the weak pointer is what says.
|
|
64
|
+
//
|
|
65
|
+
// This used to ask `RN_IS_VIEW(surfaceRoot_)`, which cannot work and is worth
|
|
66
|
+
// recording as a trap: a type check reads the instance's type pointer out of
|
|
67
|
+
// the object, so on a freed GObject it reads GLib's 0xaa poison and
|
|
68
|
+
// dereferences it. The crash was `SIGSEGV at 0xaaaaaaaaaaaaaaaa` inside
|
|
69
|
+
// RN_IS_VIEW, from this destructor, and it was latent for as long as the
|
|
70
|
+
// freed bytes at that offset happened not to fault.
|
|
71
|
+
if (surfaceRoot_ != nullptr) {
|
|
57
72
|
g_signal_handlers_disconnect_by_data(surfaceRoot_, this);
|
|
73
|
+
g_object_remove_weak_pointer(G_OBJECT(surfaceRoot_),
|
|
74
|
+
reinterpret_cast<gpointer *>(&surfaceRoot_));
|
|
75
|
+
surfaceRoot_ = nullptr;
|
|
58
76
|
}
|
|
59
77
|
// The key controller belongs to the widget, and that is exactly why it has to
|
|
60
78
|
// be disconnected: it outlives *this*, because both teardown paths reset the
|
|
@@ -77,6 +95,9 @@ void GtkFocusManager::onRootChanged(GObject * /*widget*/, GParamSpec * /*spec*/,
|
|
|
77
95
|
}
|
|
78
96
|
|
|
79
97
|
void GtkFocusManager::attachToWindow() {
|
|
98
|
+
if (surfaceRoot_ == nullptr) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
80
101
|
GtkRoot *root = gtk_widget_get_root(GTK_WIDGET(surfaceRoot_));
|
|
81
102
|
GtkWindow *window = GTK_IS_WINDOW(root) ? GTK_WINDOW(root) : nullptr;
|
|
82
103
|
if (window == window_) {
|
package/native/gtk/GtkFocus.h
CHANGED
|
@@ -102,6 +102,9 @@ class GtkFocusManager {
|
|
|
102
102
|
void emitFocus(facebook::react::Tag tag, bool focused);
|
|
103
103
|
|
|
104
104
|
GtkMountingManager *mountingManager_;
|
|
105
|
+
// The surface root, held weakly: GObject nulls this when the view is
|
|
106
|
+
// finalised, which is what makes the destructor safe to run after the root
|
|
107
|
+
// has gone. A type check cannot do that job -- see the destructor.
|
|
105
108
|
RnView *surfaceRoot_;
|
|
106
109
|
GtkEventController *keyController_{nullptr};
|
|
107
110
|
// The window this is listening to, held weakly: it outlives the manager in
|
|
@@ -15,6 +15,9 @@ struct GtkImageLoader::Pending {
|
|
|
15
15
|
std::string bytes;
|
|
16
16
|
std::string error;
|
|
17
17
|
Callback callback;
|
|
18
|
+
// Called from the worker thread as the bytes arrive, rather than from
|
|
19
|
+
// `deliver` below: see the header.
|
|
20
|
+
basalt::ImageProgress onProgress;
|
|
18
21
|
};
|
|
19
22
|
|
|
20
23
|
GtkImageLoader::GtkImageLoader() = default;
|
|
@@ -24,6 +27,10 @@ GtkImageLoader::~GtkImageLoader() {
|
|
|
24
27
|
g_clear_object(&texture);
|
|
25
28
|
}
|
|
26
29
|
cache_.clear();
|
|
30
|
+
for (auto &[uri, animation] : animations_) {
|
|
31
|
+
g_clear_object(&animation);
|
|
32
|
+
}
|
|
33
|
+
animations_.clear();
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
// Caches the texture and drops whatever that pushed out. See core/ImageCache.h
|
|
@@ -44,6 +51,11 @@ void GtkImageLoader::remember(const std::string &uri, GdkTexture *texture) {
|
|
|
44
51
|
g_clear_object(&it->second);
|
|
45
52
|
cache_.erase(it);
|
|
46
53
|
}
|
|
54
|
+
const auto animated = animations_.find(evicted);
|
|
55
|
+
if (animated != animations_.end()) {
|
|
56
|
+
g_clear_object(&animated->second);
|
|
57
|
+
animations_.erase(animated);
|
|
58
|
+
}
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
61
|
|
|
@@ -60,13 +72,23 @@ gboolean GtkImageLoader::deliver(gpointer data) {
|
|
|
60
72
|
GBytes *bytes = g_bytes_new(pending->bytes.data(), pending->bytes.size());
|
|
61
73
|
GError *decodeError = nullptr;
|
|
62
74
|
texture = gdk_texture_new_from_bytes(bytes, &decodeError);
|
|
63
|
-
g_bytes_unref(bytes);
|
|
64
75
|
if (texture == nullptr) {
|
|
65
76
|
error = decodeError != nullptr ? decodeError->message : "could not decode image";
|
|
66
77
|
g_clear_error(&decodeError);
|
|
67
78
|
} else {
|
|
79
|
+
// The same bytes again, as an animation. A second decode rather than one
|
|
80
|
+
// path for both, because `gdk_texture_new_from_bytes` is what every
|
|
81
|
+
// still image goes through and this must not change what it produces:
|
|
82
|
+
// gdk-pixbuf's loaders and GDK's are not the same set, and a PNG that
|
|
83
|
+
// decodes one way may not decode the other.
|
|
84
|
+
//
|
|
85
|
+
// Only one frame's worth of extra work for a still image, which is what
|
|
86
|
+
// nearly every <Image> is: `is_static_image` is the first thing asked,
|
|
87
|
+
// and gdk-pixbuf has already stopped at the first frame to answer it.
|
|
88
|
+
pending->loader->rememberAnimation(pending->uri, bytes);
|
|
68
89
|
pending->loader->remember(pending->uri, texture);
|
|
69
90
|
}
|
|
91
|
+
g_bytes_unref(bytes);
|
|
70
92
|
}
|
|
71
93
|
|
|
72
94
|
pending->callback(texture, error);
|
|
@@ -74,7 +96,9 @@ gboolean GtkImageLoader::deliver(gpointer data) {
|
|
|
74
96
|
return G_SOURCE_REMOVE;
|
|
75
97
|
}
|
|
76
98
|
|
|
77
|
-
void GtkImageLoader::load(const std::string &uri,
|
|
99
|
+
void GtkImageLoader::load(const std::string &uri,
|
|
100
|
+
Callback &&callback,
|
|
101
|
+
basalt::ImageProgress onProgress) {
|
|
78
102
|
if (uri.empty()) {
|
|
79
103
|
callback(nullptr, "empty source uri");
|
|
80
104
|
return;
|
|
@@ -86,12 +110,12 @@ void GtkImageLoader::load(const std::string &uri, Callback &&callback) {
|
|
|
86
110
|
return;
|
|
87
111
|
}
|
|
88
112
|
|
|
89
|
-
auto *pending = new Pending{this, uri, {}, {}, std::move(callback)};
|
|
113
|
+
auto *pending = new Pending{this, uri, {}, {}, std::move(callback), std::move(onProgress)};
|
|
90
114
|
|
|
91
115
|
std::thread([pending]() {
|
|
92
116
|
// The fetch is in core, shared with the AppKit loader: a URI means the same
|
|
93
117
|
// thing on both, and only the decode below differs.
|
|
94
|
-
fetchImageBytes(pending->uri, &pending->bytes, &pending->error);
|
|
118
|
+
fetchImageBytes(pending->uri, &pending->bytes, &pending->error, pending->onProgress);
|
|
95
119
|
g_idle_add_full(G_PRIORITY_DEFAULT, deliver, pending, nullptr);
|
|
96
120
|
}).detach();
|
|
97
121
|
}
|
|
@@ -116,6 +140,53 @@ void GtkImageLoader::loadImage(const std::string &uri,
|
|
|
116
140
|
});
|
|
117
141
|
}
|
|
118
142
|
|
|
143
|
+
// Decodes `bytes` as an animation and keeps it when it turns out to be one.
|
|
144
|
+
//
|
|
145
|
+
// Through a memory input stream, which is the only entry point gdk-pixbuf
|
|
146
|
+
// offers for animation-from-bytes. It reads synchronously, and on the main
|
|
147
|
+
// thread, for the same reason the texture above is decoded there: what comes
|
|
148
|
+
// out is a GObject whose frames become GdkTextures, and both are only safe on
|
|
149
|
+
// the thread that will use them. The fetch, which is the slow part, already
|
|
150
|
+
// happened on the worker.
|
|
151
|
+
void GtkImageLoader::rememberAnimation(const std::string &uri, GBytes *bytes) {
|
|
152
|
+
if (animations_.find(uri) != animations_.end()) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// gdk-pixbuf 2.44 deprecated its whole animation API, with nothing in
|
|
157
|
+
// gdk-pixbuf to replace it: GNOME's direction is glycin, a separate library
|
|
158
|
+
// and a new dependency, and GTK4 itself has no frame source for a GIF at all
|
|
159
|
+
// -- `gtk_image_set_from_file` on one shows a still. So this uses the
|
|
160
|
+
// deprecated API deliberately rather than by accident, and
|
|
161
|
+
// docs/backlog/image.md records what replacing it would take.
|
|
162
|
+
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
|
163
|
+
|
|
164
|
+
GInputStream *stream = g_memory_input_stream_new_from_bytes(bytes);
|
|
165
|
+
GError *error = nullptr;
|
|
166
|
+
GdkPixbufAnimation *animation =
|
|
167
|
+
gdk_pixbuf_animation_new_from_stream(stream, nullptr, &error);
|
|
168
|
+
g_object_unref(stream);
|
|
169
|
+
if (animation == nullptr) {
|
|
170
|
+
// Not an error worth reporting: the still decode above already succeeded,
|
|
171
|
+
// and a format gdk-pixbuf has no loader for is simply not animated.
|
|
172
|
+
g_clear_error(&error);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (gdk_pixbuf_animation_is_static_image(animation) == TRUE) {
|
|
177
|
+
g_object_unref(animation);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
animations_[uri] = animation;
|
|
182
|
+
G_GNUC_END_IGNORE_DEPRECATIONS
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
GdkPixbufAnimation *GtkImageLoader::animation(const std::string &uri) {
|
|
186
|
+
const auto found = animations_.find(uri);
|
|
187
|
+
return found == animations_.end() ? nullptr : found->second;
|
|
188
|
+
}
|
|
189
|
+
|
|
119
190
|
facebook::react::IImageLoader::CacheStatus GtkImageLoader::getCacheStatus(
|
|
120
191
|
const std::string &uri) {
|
|
121
192
|
// Memory or nothing: there is no disk cache here, and saying `Disk` would be
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
#include <string>
|
|
31
31
|
#include <unordered_map>
|
|
32
32
|
|
|
33
|
+
#include "ImageBytes.h"
|
|
33
34
|
#include "ImageCache.h"
|
|
34
35
|
|
|
35
36
|
#include <react/io/IImageLoader.h>
|
|
@@ -55,7 +56,32 @@ class GtkImageLoader : public facebook::react::IImageLoader {
|
|
|
55
56
|
GtkImageLoader(GtkImageLoader &&) = delete;
|
|
56
57
|
GtkImageLoader &operator=(GtkImageLoader &&) = delete;
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
// `onProgress` is optional and is `basalt::ImageProgress`: how much of an
|
|
60
|
+
// http fetch has arrived, called from the fetching thread. Only an http URI
|
|
61
|
+
// reports anything; see core/ImageBytes.h.
|
|
62
|
+
//
|
|
63
|
+
// Not marshalled to the main thread, unlike the callback above, and that is
|
|
64
|
+
// the point of it: a Fabric event emitter may be dispatched from any thread,
|
|
65
|
+
// and a progress tick that had to wait for the main loop would arrive after
|
|
66
|
+
// the image it is about.
|
|
67
|
+
void load(const std::string &uri,
|
|
68
|
+
Callback &&callback,
|
|
69
|
+
basalt::ImageProgress onProgress = {});
|
|
70
|
+
|
|
71
|
+
// The animation behind an animated image, or null.
|
|
72
|
+
//
|
|
73
|
+
// Separate from `load` because being animated is a property of the file
|
|
74
|
+
// rather than of the request: `load` answers with the first frame, which is
|
|
75
|
+
// what a still <Image> paints and what an animated one starts on, and this
|
|
76
|
+
// says whether there is more. Both come out of the same decode.
|
|
77
|
+
//
|
|
78
|
+
// gdk-pixbuf owns the frames, and offers an iterator with a clock rather than
|
|
79
|
+
// indexed access, which is why this hands over the animation itself where the
|
|
80
|
+
// AppKit loader hands over an array of frames. The object is the loader's and
|
|
81
|
+
// is the same object each time, which is what lets a view tell a re-mount
|
|
82
|
+
// from a new animation. Good until the next load, since an eviction can take
|
|
83
|
+
// it away.
|
|
84
|
+
GdkPixbufAnimation *animation(const std::string &uri);
|
|
59
85
|
|
|
60
86
|
// --- IImageLoader ----------------------------------------------------------
|
|
61
87
|
//
|
|
@@ -77,8 +103,19 @@ class GtkImageLoader : public facebook::react::IImageLoader {
|
|
|
77
103
|
//
|
|
78
104
|
// Caches a texture and releases whatever the policy dropped.
|
|
79
105
|
void remember(const std::string &uri, GdkTexture *texture);
|
|
106
|
+
void rememberAnimation(const std::string &uri, GBytes *bytes);
|
|
80
107
|
|
|
81
108
|
std::unordered_map<std::string, GdkTexture *> cache_;
|
|
109
|
+
// Only the files that turned out to have more than one frame, so a still
|
|
110
|
+
// image costs nothing. Keyed by the same URI and dropped by the same
|
|
111
|
+
// eviction.
|
|
112
|
+
//
|
|
113
|
+
// The frames inside are gdk-pixbuf's and are not measured by the cache
|
|
114
|
+
// policy: there is no way to ask a GdkPixbufAnimation how much it is holding,
|
|
115
|
+
// and the AppKit side can because it decoded the frames itself. So an
|
|
116
|
+
// animated image is accounted as its first frame on this host. Recorded in
|
|
117
|
+
// docs/backlog/image.md rather than guessed at.
|
|
118
|
+
std::unordered_map<std::string, GdkPixbufAnimation *> animations_;
|
|
82
119
|
// Which URI goes next, and when. The textures are this class's; the decision
|
|
83
120
|
// is shared with the other two hosts. See core/ImageCache.h.
|
|
84
121
|
basalt::ImageCachePolicy policy_;
|