diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4419ff22ff..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,40 +0,0 @@ -dist: xenial - -language: generic -sudo: required - -cache: - directories: - - $HOME/.m2 - -services: - - docker - -branches: - only: - - master - - develop - -install: - - curl -O https://download.clojure.org/install/linux-install-1.10.1.447.sh - - chmod +x linux-install-1.10.1.447.sh - - sudo ./linux-install-1.10.1.447.sh - -before_script: - - env | sort - -script: - - ./manage.sh build-devenv - - ./manage.sh run-frontend-tests - - ./manage.sh run-backend-tests - - ./manage.sh build-images - - ./manage.sh run - -after_script: - - docker images - -notifications: - email: false - -env: - - NODE_VERSION=10.16.0 diff --git a/docker/images/docker-compose.yaml b/docker/images/docker-compose.yaml index 2e9628f613..8076064b94 100644 --- a/docker/images/docker-compose.yaml +++ b/docker/images/docker-compose.yaml @@ -152,9 +152,9 @@ services: # AWS_ACCESS_KEY_ID: # AWS_SECRET_ACCESS_KEY: - # PENPOT_ASSETS_STORAGE_BACKEND: assets-s3 - # PENPOT_STORAGE_ASSETS_S3_ENDPOINT: - # PENPOT_STORAGE_ASSETS_S3_BUCKET: + # PENPOT_OBJECTS_STORAGE_BACKEND: s3 + # PENPOT_OBJECTS_STORAGE_S3_ENDPOINT: + # PENPOT_OBJECTS_STORAGE_S3_BUCKET: ## Telemetry. When enabled, a periodical process will send anonymous data about this ## instance. Telemetry data will enable us to learn how the application is used, diff --git a/docs/technical-guide/configuration.md b/docs/technical-guide/configuration.md index 648df7ea67..f48117b071 100644 --- a/docs/technical-guide/configuration.md +++ b/docs/technical-guide/configuration.md @@ -114,14 +114,7 @@ configuration. The callback has the following format: ```html -https:///api/auth/oauth//callback -``` - -You will need to change and according to your setup. -This is how it looks with Gitlab provider: - -```html -https:///api/auth/oauth/gitlab/callback +https:///api/auth/oidc/callback ``` #### Google diff --git a/frontend/resources/templates/index.mustache b/frontend/resources/templates/index.mustache index f6636c9e9e..d6437a8969 100644 --- a/frontend/resources/templates/index.mustache +++ b/frontend/resources/templates/index.mustache @@ -17,17 +17,18 @@ - + {{#isDebug}} - + {{/isDebug}} - + diff --git a/frontend/resources/templates/rasterizer.mustache b/frontend/resources/templates/rasterizer.mustache index 56e2adaf7a..90a7f1dfdc 100644 --- a/frontend/resources/templates/rasterizer.mustache +++ b/frontend/resources/templates/rasterizer.mustache @@ -3,10 +3,11 @@ Penpot - Rasterizer - + diff --git a/frontend/resources/templates/render.mustache b/frontend/resources/templates/render.mustache index cf938583ec..4de213f9ad 100644 --- a/frontend/resources/templates/render.mustache +++ b/frontend/resources/templates/render.mustache @@ -4,10 +4,12 @@ Penpot - Render - + + diff --git a/frontend/scripts/_helpers.js b/frontend/scripts/_helpers.js index 1065327d83..c1c018c74c 100644 --- a/frontend/scripts/_helpers.js +++ b/frontend/scripts/_helpers.js @@ -27,9 +27,11 @@ export function startWorker() { }); } -export const isDebug = process.env.NODE_ENV !== "production"; -export const CURRENT_VERSION = process.env.CURRENT_VERSION || "develop"; -export const BUILD_DATE = process.env.BUILD_DATE || "" + new Date(); +export const IS_DEBUG = process.env.NODE_ENV !== "production"; +export const BUILD_DATE = process.env.BUILD_DATE || (new Date().toString()) ; +export const BUILD_TS = process.env.BUILD_TS || Date.now(); +export const VERSION = process.env.VERSION || "develop"; +export const VERSION_TAG = process.env.VERSION_TAG || VERSION; async function findFiles(basePath, predicate, options = {}) { predicate = @@ -193,25 +195,25 @@ async function generateManifest() { render_main: "./js/render.js", rasterizer_main: "./js/rasterizer.js", - config: "./js/config.js?version=" + CURRENT_VERSION, - polyfills: "./js/polyfills.js?version=" + CURRENT_VERSION, - libs: "./js/libs.js?version=" + CURRENT_VERSION, - worker_main: "./js/worker/main.js?version=" + CURRENT_VERSION, - default_translations: "./js/translation.en.js?version=" + CURRENT_VERSION, + config: "./js/config.js?version=" + VERSION_TAG, + polyfills: "./js/polyfills.js?version=" + VERSION_TAG, + libs: "./js/libs.js?version=" + VERSION_TAG, + worker_main: "./js/worker/main.js?version=" + VERSION_TAG, + default_translations: "./js/translation.en.js?version=" + VERSION_TAG, importmap: JSON.stringify({ "imports": { - "./js/shared.js": "./js/shared.js?version=" + CURRENT_VERSION, - "./js/main.js": "./js/main.js?version=" + CURRENT_VERSION, - "./js/render.js": "./js/render.js?version=" + CURRENT_VERSION, - "./js/render-wasm.js": "./js/render-wasm.js?version=" + CURRENT_VERSION, - "./js/rasterizer.js": "./js/rasterizer.js?version=" + CURRENT_VERSION, - "./js/main-dashboard.js": "./js/main-dashboard.js?version=" + CURRENT_VERSION, - "./js/main-auth.js": "./js/main-auth.js?version=" + CURRENT_VERSION, - "./js/main-viewer.js": "./js/main-viewer.js?version=" + CURRENT_VERSION, - "./js/main-settings.js": "./js/main-settings.js?version=" + CURRENT_VERSION, - "./js/main-workspace.js": "./js/main-workspace.js?version=" + CURRENT_VERSION, - "./js/util-highlight.js": "./js/util-highlight.js?version=" + CURRENT_VERSION + "./js/shared.js": "./js/shared.js?version=" + VERSION_TAG, + "./js/main.js": "./js/main.js?version=" + VERSION_TAG, + "./js/render.js": "./js/render.js?version=" + VERSION_TAG, + "./js/render-wasm.js": "./js/render-wasm.js?version=" + VERSION_TAG, + "./js/rasterizer.js": "./js/rasterizer.js?version=" + VERSION_TAG, + "./js/main-dashboard.js": "./js/main-dashboard.js?version=" + VERSION_TAG, + "./js/main-auth.js": "./js/main-auth.js?version=" + VERSION_TAG, + "./js/main-viewer.js": "./js/main-viewer.js?version=" + VERSION_TAG, + "./js/main-settings.js": "./js/main-settings.js?version=" + VERSION_TAG, + "./js/main-workspace.js": "./js/main-workspace.js?version=" + VERSION_TAG, + "./js/util-highlight.js": "./js/util-highlight.js?version=" + VERSION_TAG } }) }; @@ -222,11 +224,12 @@ async function generateManifest() { async function renderTemplate(path, context = {}, partials = {}) { const content = await fs.readFile(path, { encoding: "utf-8" }); - const ts = Math.floor(new Date()); - context = Object.assign({}, context, { - ts: ts, - isDebug, + isDebug: IS_DEBUG, + version: VERSION, + version_tag: VERSION_TAG, + build_date: BUILD_DATE, + build_ts: BUILD_TS, }); return mustache.render(content, context, partials); @@ -390,7 +393,6 @@ async function generateSvgSprites() { } async function generateTemplates() { - const isDebug = process.env.NODE_ENV !== "production"; await fs.mkdir("./resources/public/", { recursive: true }); const manifest = await generateManifest(); @@ -415,10 +417,7 @@ async function generateTemplates() { }; const context = { - manifest: manifest, - version: CURRENT_VERSION, - build_date: BUILD_DATE, - isDebug, + manifest: manifest }; content = await renderTemplate( @@ -487,7 +486,7 @@ export async function compileStyles() { await fs.mkdir("./resources/public/css", { recursive: true }); await fs.writeFile("./resources/public/css/main.css", result); - if (isDebug) { + if (IS_DEBUG) { let debugCSS = await compileSassDebug(worker); await fs.writeFile("./resources/public/css/debug.css", debugCSS); } diff --git a/frontend/scripts/build b/frontend/scripts/build index 613bb29fd2..eb0d2dd221 100755 --- a/frontend/scripts/build +++ b/frontend/scripts/build @@ -2,26 +2,26 @@ # NOTE: this script should be called from the parent directory to # properly work. +set -ex + export INCLUDE_STORYBOOK=${BUILD_STORYBOOK:-no}; export INCLUDE_WASM=${BUILD_WASM:-yes}; -export CURRENT_VERSION=$1; -export BUILD_DATE=$(date -R); -export CURRENT_HASH=${CURRENT_HASH:-$(git rev-parse --short HEAD)}; export EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS; -export TS=$(date +%s); + +export BUILD_DATE=$(date -R); +export BUILD_TS=$(date +%s); + +export VERSION=${1:-develop}; +export VERSION_TAG="${VERSION}-${BUILD_TS}"; # Some cljs reacts on this environment variable for define more # performant code on macros (example: rumext) export NODE_ENV=production; -echo "Current path:" -echo $PATH - -set -ex corepack enable; corepack install; -yarn install || exit 1; +yarn install; rm -rf target/dist; rm -rf resources/public; @@ -37,7 +37,7 @@ yarn run build:app:main $EXTRA_PARAMS; yarn run build:app:libs; yarn run build:app:assets; -sed -i "s/\.\/render.js/.\/render.js?version=$CURRENT_VERSION/g" resources/public/js/worker/main*.js +sed -i "s/\.\/render.js/.\/render.js?version=$VERSION_TAG/g" resources/public/js/worker/main*.js rsync -avr resources/public/ target/dist/ diff --git a/frontend/scripts/build-storybook b/frontend/scripts/build-storybook index 8588fd7866..1b4a0ab791 100755 --- a/frontend/scripts/build-storybook +++ b/frontend/scripts/build-storybook @@ -2,18 +2,16 @@ # NOTE: this script should be called from the parent directory to # properly work. -export CURRENT_VERSION=$1; +set -ex + +export BUILD_TS=$(date +%s); export BUILD_DATE=$(date -R); -export CURRENT_HASH=${CURRENT_HASH:-$(git rev-parse --short HEAD)}; -export TS=$(date +%s); + +export VERSION=${1:-develop}; +export VERSION_TAG="${VERSION}-${BUILD_TS}"; export NODE_ENV=production; -echo "Current path:" -echo $PATH - -set -ex - corepack enable; corepack install || exit 1; yarn install || exit 1; diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 9e71ac802e..a1e6a6afda 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -95,6 +95,7 @@ (def browser (parse-browser)) (def platform (parse-platform)) +(def version-tag (obj/get global "penpotVersionTag")) (def terms-of-service-uri (obj/get global "penpotTermsOfServiceURI")) (def privacy-policy-uri (obj/get global "penpotPrivacyPolicyURI")) (def flex-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/")) @@ -190,9 +191,8 @@ (defn resolve-href [resource] - (let [version (get version :full) - href (-> public-uri - (u/ensure-path-slash) - (u/join resource) - (get :path))] - (str href "?version=" version))) + (let [href (-> public-uri + (u/ensure-path-slash) + (u/join resource) + (get :path))] + (str href "?version=" version-tag))) diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index 55f7c9a661..2bebf8e4b8 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -27,8 +27,10 @@ [app.main.data.workspace.colors :as wdc] [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] + [app.main.data.workspace.texts :as dwt] [app.main.data.workspace.transforms :as dwtr] [app.main.data.workspace.undo :as dwu] + [app.main.features :as features] [app.main.fonts :as fonts] [app.main.store :as st] [app.util.i18n :refer [tr]] @@ -300,11 +302,20 @@ update-fn (fn [node _] (-> node (d/txt-merge txt-attrs) - (cty/remove-typography-from-node)))] - (dwsh/update-shapes shape-ids - #(txt/update-text-content % update-node? update-fn nil) - {:ignore-touched true - :page-id page-id}))) + (cty/remove-typography-from-node))) + ;; Check if any attribute affects text layout (requires resize) + affects-layout? (some #(contains? txt-attrs %) [:font-size :font-family :font-weight :letter-spacing :line-height])] + (ptk/reify ::generate-text-shape-update + ptk/WatchEvent + (watch [_ state _] + (cond-> (rx/of (dwsh/update-shapes shape-ids + #(txt/update-text-content % update-node? update-fn nil) + {:ignore-touched true + :page-id page-id})) + (and affects-layout? + (features/active-feature? state "render-wasm/v1")) + (rx/merge + (rx/of (dwt/resize-wasm-text-all shape-ids)))))))) (defn update-line-height ([value shape-ids attributes] (update-line-height value shape-ids attributes nil)) @@ -353,11 +364,17 @@ (-> node (d/txt-merge txt-attrs) (cty/remove-typography-from-node))))] - (dwsh/update-shapes shape-ids - (fn [shape] - (txt/update-text-content shape update-node? #(update-fn %1 (ctst/font-weight-applied? shape)) nil)) - {:ignore-touched true - :page-id page-id}))) + (ptk/reify ::generate-font-family-text-shape-update + ptk/WatchEvent + (watch [_ state _] + (cond-> (rx/of (dwsh/update-shapes shape-ids + (fn [shape] + (txt/update-text-content shape update-node? #(update-fn %1 (ctst/font-weight-applied? shape)) nil)) + {:ignore-touched true + :page-id page-id})) + (features/active-feature? state "render-wasm/v1") + (rx/merge + (rx/of (dwt/resize-wasm-text-all shape-ids)))))))) (defn- create-font-family-text-attrs [value] @@ -425,10 +442,16 @@ (-> node (d/txt-merge txt-attrs) (cty/remove-typography-from-node))))] - (dwsh/update-shapes shape-ids - #(txt/update-text-content % update-node? update-fn nil) - {:ignore-touched true - :page-id page-id}))) + (ptk/reify ::generate-font-weight-text-shape-update + ptk/WatchEvent + (watch [_ state _] + (cond-> (rx/of (dwsh/update-shapes shape-ids + #(txt/update-text-content % update-node? update-fn nil) + {:ignore-touched true + :page-id page-id})) + (features/active-feature? state "render-wasm/v1") + (rx/merge + (rx/of (dwt/resize-wasm-text-all shape-ids)))))))) (defn update-font-weight ([value shape-ids attributes] (update-font-weight value shape-ids attributes nil)) diff --git a/frontend/src/app/main/ui/forms.cljs b/frontend/src/app/main/ui/forms.cljs index 4988f589d4..6e49615470 100644 --- a/frontend/src/app/main/ui/forms.cljs +++ b/frontend/src/app/main/ui/forms.cljs @@ -23,6 +23,7 @@ touched? (and (contains? (:data @form) input-name) (get-in @form [:touched input-name])) + error (get-in @form [:errors input-name]) value (get-in @form [:data input-name] "") @@ -52,7 +53,8 @@ (let [form (mf/use-ctx context) disabled? (or (and (some? form) (or (not (:valid @form)) - (seq (:external-errors @form)))) + (seq (:async-errors @form)) + (seq (:extra-errors @form)))) (true? disabled)) handle-key-down-save (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs index d45afcd9c2..2e57f197be 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs @@ -236,12 +236,14 @@ (on-composite-input-change form field value false)) ([form field value trim?] (letfn [(clean-errors [errors] - (-> errors - (dissoc field) - (not-empty)))] + (some-> errors + (update :value #(when (map? %) (dissoc % field))) + (update :value #(when (seq %) %)) + (not-empty)))] (swap! form (fn [state] (-> state (assoc-in [:data :value field] (if trim? (str/trim value) value)) + (assoc-in [:touched :value field] true) (update :errors clean-errors) (update :extra-errors clean-errors))))))) @@ -257,6 +259,9 @@ value (get-in @form [:data :value input-name] "") + touched? + (get-in @form [:touched :value input-name]) + resolve-stream (mf/with-memo [token] (if-let [value (get-in token [:value input-name])] @@ -284,7 +289,7 @@ :hint-message (:message hint) :hint-type (:type hint)}) props - (if error + (if (and touched? error) (mf/spread-props props {:hint-type "error" :hint-message (:message error)}) props) @@ -332,6 +337,7 @@ message (tr "workspace.tokens.resolved-value" (or resolved-value value))] (swap! form update :errors dissoc :value) (swap! form update :extra-errors dissoc :value) + (swap! form update :async-errors dissoc :reference) (if (= input-value (str resolved-value)) (reset! hint* {}) (reset! hint* {:message message :type "hint"})))))))] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs index 69ade9d635..53a7f1cd2d 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs @@ -105,13 +105,6 @@ active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite)) active-tab (deref active-tab*) - on-toggle-tab - (mf/use-fn - (mf/deps) - (fn [new-tab] - (let [new-tab (keyword new-tab)] - (reset! active-tab* new-tab)))) - token (mf/with-memo [token] (or token {:type token-type})) @@ -151,6 +144,17 @@ (fm/use-form :schema schema :initial initial) + on-toggle-tab + (mf/use-fn + (mf/deps form) + (fn [new-tab] + (let [new-tab (keyword new-tab)] + (if (= new-tab :reference) + (swap! form assoc-in [:async-errors :reference] + {:message "Need valid reference"}) + (swap! form update :async-errors dissoc :reference)) + (reset! active-tab* new-tab)))) + on-cancel (mf/use-fn (fn [e] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs index 41e5e01640..1d8f6e9dff 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs @@ -291,6 +291,7 @@ [:color {:optional true} [:maybe :string]] [:color-result {:optional true} ::sm/any] [:inset {:optional true} [:maybe :boolean]]]]] + (if (= active-tab :reference) [:reference {:optional false} ::sm/text] [:reference {:optional true} [:maybe :string]])]] diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index f4794ef6e8..ec39712420 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -1185,7 +1185,6 @@ {:cmd :export-shapes :profile-id (:profile-id @st/state) :wait true - :skip-children (:skip-children value false) :exports [{:file-id file-id :page-id page-id :object-id id diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 61826a798e..9eab6a6018 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -114,7 +114,7 @@ (defn- load [locale] - (let [path (str "./translation." locale ".js?version=" (:full cf/version))] + (let [path (str "./translation." locale ".js?version=" cf/version-tag)] (->> (mod/import path) (p/fmap (fn [result] (unchecked-get result "default"))) (p/fnly (fn [data cause] diff --git a/render-wasm/_build_env b/render-wasm/_build_env index d18526caa1..f8e0faf598 100644 --- a/render-wasm/_build_env +++ b/render-wasm/_build_env @@ -1,6 +1,6 @@ #!/usr/bin/env bash -export CURRENT_VERSION=${CURRENT_VERSION:-develop}; +export VERSION_TAG=${VERSION:-develop}; if [ "$NODE_ENV" = "production" ]; then export BUILD_MODE="release"; @@ -81,7 +81,7 @@ function copy_artifacts { cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js $DEST/$BUILD_NAME.js; cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.wasm $DEST/$BUILD_NAME.wasm; - sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$CURRENT_VERSION/g" $DEST/$BUILD_NAME.js; + sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$VERSION_TAG/g" $DEST/$BUILD_NAME.js; yarn esbuild target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js \ --log-level=error \ diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 9d4ff41617..162732d551 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -284,6 +284,7 @@ pub extern "C" fn set_view_end() { performance::end_measure!("set_view_end::clear_tile_index"); performance::end_timed_log!("clear_tile_index", _clear_start); } + state.render_state.sync_cached_viewbox(); performance::end_measure!("set_view_end"); performance::end_timed_log!("set_view_end", _end_start); #[cfg(feature = "profile-macros")] diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 1d80780bb1..f0619a9e37 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1136,6 +1136,7 @@ impl RenderState { ) -> Result<(), String> { let _start = performance::begin_timed_log!("start_render_loop"); let scale = self.get_scale(); + self.tile_viewbox.update(self.viewbox, scale); self.focus_mode.reset(); @@ -2292,6 +2293,10 @@ impl RenderState { (self.viewbox.zoom - self.cached_viewbox.zoom).abs() > f32::EPSILON } + pub fn sync_cached_viewbox(&mut self) { + self.cached_viewbox = self.viewbox; + } + pub fn mark_touched(&mut self, uuid: Uuid) { self.touched_ids.insert(uuid); } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 7c757a4e5e..eabbd0dcd4 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -1529,6 +1529,7 @@ impl Shape { || !self.transform.is_identity() || !math::is_close_to(self.rotation, 0.0) || matches!(self.shape_type, Type::Group(_) | Type::Frame(_)) + || matches!(self.shape_type, Type::Text(_)) } pub fn count_visible_inner_strokes(&self) -> usize { diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 40b272007c..f178ed4477 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -100,6 +100,16 @@ impl<'a> State<'a> { } pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> { + // If zoom changed, we MUST rebuild the tile index before using it. + // Otherwise, the index will have tiles from the old zoom level, causing visible + // tiles to appear empty. This can happen if start_render_loop() is called before + // set_view_end() finishes rebuilding the index, or if set_view_end() hasn't been + // called yet. + let zoom_changed = self.render_state.zoom_changed(); + if zoom_changed { + self.rebuild_tiles_shallow(); + } + self.render_state .start_render_loop(None, &self.shapes, timestamp, false)?; Ok(())