diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 4bae9a28fe..388b04312d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -1036,6 +1036,11 @@ bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event) Vec2d mouse_pos = mouse_coord.cast(); if (mouse_event.Moving()) { + // No button is down, yet a stroke is still open: its release went elsewhere, as a paint drag does not + // capture the mouse. End the stroke as that release would have. + if (m_button_down != Button::None) + gizmo_event(m_button_down == Button::Left ? SLAGizmoEventType::LeftUp : SLAGizmoEventType::RightUp, mouse_pos, + mouse_event.ShiftDown(), mouse_event.AltDown(), false); gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false); return false; } @@ -1205,6 +1210,10 @@ void GLGizmoPainterBase::on_set_state() m_old_mo_id = -1; //m_iva.release_geometry(); m_triangle_selectors.clear(); + // A stroke still open goes with the selectors. Its state must not carry into the next session, where the + // first move would end it as if released and the first press would paint a line from its last position. + m_button_down = Button::None; + m_last_mouse_click = Vec2d::Zero(); //Camera& camera = wxGetApp().plater()->get_camera(); //camera.look_at(camera.get_position(), m_previous_target, Vec3d::UnitZ()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp index 45ff19480f..768f16f567 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp @@ -562,6 +562,11 @@ void GLGizmoTextureDisplacement::on_shutdown() m_shaded_island_delta = Eigen::Matrix::Identity(); m_island_drag_active = false; m_island_move_set.clear(); + // The pane closes below, taking the highlight with it, and the view must not open the next session in a + // Fast the user never picked. Set directly: nothing is to be queued against a gizmo that is closing. + if (m_island_view.switched) + m_use_shaded_preview = false; + m_island_view = IslandViewFollow{}; m_adjust_texture_mode = false; m_seam_edit_mode = false; m_adjust_drag_handle = AdjustHandle::None; @@ -727,18 +732,13 @@ void GLGizmoTextureDisplacement::render_painter_gizmo() // The UV editor's island selection, shown on the model. Polled here rather than pushed: the pane // changes its selection in its own mouse handling, and a compare of a few ints per frame is free. - { - const TextureDisplacementLayer *al = active_layer(); - const UVEditorCanvas *uv_canvas = wxGetApp().plater()->get_uv_editor_canvas(); - if (m_show_uv_editor && al != nullptr && al->projection_method == TextureProjectionMethod::LSCM && - uv_canvas != nullptr && !m_uv_editor_unwrap.empty()) { - if (uv_canvas->selected_islands() != m_island_overlay_selection) - rebuild_island_overlay(uv_canvas->selected_islands()); - render_island_overlay(); - } else if (m_island_overlay_glmodel.is_initialized()) { - m_island_overlay_glmodel.reset(); - m_island_overlay_selection.clear(); - } + if (const std::vector *islands = island_highlight_selection()) { + if (*islands != m_island_overlay_selection) + rebuild_island_overlay(*islands); + render_island_overlay(); + } else if (m_island_overlay_glmodel.is_initialized()) { + m_island_overlay_glmodel.reset(); + m_island_overlay_selection.clear(); } // Diagnostic overlays, drawn on top of whatever preview is active (both pull toward the camera @@ -1520,7 +1520,7 @@ void GLGizmoTextureDisplacement::render_shaded_preview_mesh() // is box-filtered down to 128 px for a ~48 px row, and feeding it to the shader cost the preview // three quarters of the height map's detail - and, since height_tex_texel is derived from it, also // flattened the shading gradient and made the parallax march skip itself at angles where it should - // run. Cached on the image_data pointer + smoothing, so no PNG is decoded per frame. + // run. Cached per image_data and smoothing, so no PNG is decoded per frame. GLTexture *tex = get_layer_height_texture(*layer); if (tex == nullptr || tex->get_width() <= 0 || tex->get_height() <= 0) return; @@ -1926,6 +1926,16 @@ void GLGizmoTextureDisplacement::render_paint_overlay(GLModel &overlay) shader->stop_using(); } +const std::vector *GLGizmoTextureDisplacement::island_highlight_selection() +{ + const TextureDisplacementLayer *al = active_layer(); + const UVEditorCanvas *uv_canvas = wxGetApp().plater()->get_uv_editor_canvas(); + if (m_show_uv_editor && al != nullptr && al->projection_method == TextureProjectionMethod::LSCM && + uv_canvas != nullptr && !m_uv_editor_unwrap.empty()) + return &uv_canvas->selected_islands(); + return nullptr; +} + void GLGizmoTextureDisplacement::rebuild_island_overlay(const std::vector &selection) { m_island_overlay_glmodel.reset(); @@ -2651,6 +2661,37 @@ void GLGizmoTextureDisplacement::apply_view_mode(int mode) m_parent.set_as_dirty(); } +int GLGizmoTextureDisplacement::view_mode() const +{ + return m_use_shaded_preview ? 1 : + m_uv_check_mode == UVCheckMode::Checker ? 2 : + m_uv_check_mode == UVCheckMode::Distortion ? 3 : 0; +} + +int GLGizmoTextureDisplacement::IslandViewFollow::step(bool shown_now, int view, bool debug_open) +{ + shown = shown_now; + if (!shown) { + // The view is back to Normal already if a debug stage took it, and Normal applied over a stage would + // queue a preview job that replaces the stage being inspected. + const bool restore = switched && view == 1; + switched = keep_normal = false; + return restore ? 0 : -1; + } + if (view != 0 || keep_normal || debug_open) + return -1; + switched = true; + return 1; +} + +void GLGizmoTextureDisplacement::follow_island_highlight() +{ + const std::vector *islands = island_highlight_selection(); + const int view = m_island_view.step(islands != nullptr && !islands->empty(), view_mode(), m_debug_stage >= 0); + if (view >= 0) + apply_view_mode(view); +} + void GLGizmoTextureDisplacement::push_uv_pane_state() { UVEditorCanvas *canvas = wxGetApp().plater()->get_uv_editor_canvas(); @@ -2891,7 +2932,8 @@ void GLGizmoTextureDisplacement::run_uv_command(int cmd, float value) rebuild_preview(); } } - // FrameAll/ToggleSnap are handled inside the canvas; ProjectFromView is not wired yet. + // FrameAll/ToggleSnap are handled inside the canvas; ProjectFromView is not wired yet. SelectionChanged + // only asks for this frame: follow_island_highlight() and the overlay read the selection back themselves. m_parent.set_as_dirty(); } @@ -4038,8 +4080,8 @@ void GLGizmoTextureDisplacement::remove_texture_layer(int slot) if (slot >= 0 && slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS)) { mv->texture_displacement_facet(slot).reset(); m_thumbnails[size_t(slot)].reset(); - m_thumbnail_source[size_t(slot)] = nullptr; - m_layer_expanded[size_t(slot)] = false; + m_thumbnail_source[size_t(slot)].reset(); + m_layer_expanded[size_t(slot)] = false; } if (m_picker_slot == slot) m_picker_slot = -1; @@ -4213,7 +4255,7 @@ int GLGizmoTextureDisplacement::select_visible_faces(const std::array selector.reset(); for (unsigned i : unobscured) selector.set_facet(int(front_facing[i]), EnforcerBlockerType::ENFORCER); - selector.request_update_render_data(); + selector.request_update_render_data(true); update_model_object(); m_parent.set_as_dirty(); @@ -4236,7 +4278,7 @@ void GLGizmoTextureDisplacement::select_whole_model() const size_t facet_count = v->mesh().its.indices.size(); for (size_t i = 0; i < facet_count; ++i) m_triangle_selectors[idx]->set_facet(int(i), EnforcerBlockerType::ENFORCER); - m_triangle_selectors[idx]->request_update_render_data(); + m_triangle_selectors[idx]->request_update_render_data(true); } update_model_object(); m_parent.set_as_dirty(); @@ -5342,7 +5384,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_thumbnail(const TextureDisplace return nullptr; const size_t slot = size_t(layer.slot); - if (m_thumbnails[slot] && m_thumbnail_source[slot] == layer.image_data.get() && + if (m_thumbnails[slot] && m_thumbnail_source[slot].lock() == layer.image_data && m_thumbnail_smoothing[slot] == layer.smoothing) return m_thumbnails[slot].get(); @@ -5355,7 +5397,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_thumbnail(const TextureDisplace return nullptr; m_thumbnails[slot] = std::move(texture); - m_thumbnail_source[slot] = layer.image_data.get(); + m_thumbnail_source[slot] = layer.image_data; m_thumbnail_smoothing[slot] = layer.smoothing; return m_thumbnails[slot].get(); } @@ -5364,17 +5406,17 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_color_texture(const TextureDisp { if (layer.empty() || !layer.color_enabled) return nullptr; - if (m_color_tex && m_color_tex_source == layer.image_data.get() && m_color_tex_smoothing == layer.smoothing) + if (m_color_tex && m_color_tex_source.lock() == layer.image_data && m_color_tex_smoothing == layer.smoothing) return m_color_tex.get(); std::unique_ptr texture = upload_color_texture(decode_height_texture(layer), HEIGHT_TEX_MAX_PX); if (!texture) { m_color_tex.reset(); - m_color_tex_source = nullptr; + m_color_tex_source.reset(); return nullptr; } m_color_tex = std::move(texture); - m_color_tex_source = layer.image_data.get(); + m_color_tex_source = layer.image_data; m_color_tex_smoothing = layer.smoothing; return m_color_tex.get(); } @@ -5386,7 +5428,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_height_texture(const TextureDis // One slot, not one per layer: the preview shader only ever shades the *active* layer, so a single // full-resolution upload is enough and the VRAM cost stays at one texture rather than eight. - if (m_height_tex && m_height_tex_source == layer.image_data.get() && m_height_tex_smoothing == layer.smoothing) + if (m_height_tex && m_height_tex_source.lock() == layer.image_data && m_height_tex_smoothing == layer.smoothing) return m_height_tex.get(); std::unique_ptr texture = upload_height_thumbnail(decode_height_texture(layer), HEIGHT_TEX_MAX_PX); @@ -5394,7 +5436,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_height_texture(const TextureDis return nullptr; m_height_tex = std::move(texture); - m_height_tex_source = layer.image_data.get(); + m_height_tex_source = layer.image_data; m_height_tex_smoothing = layer.smoothing; return m_height_tex.get(); } @@ -5835,6 +5877,9 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float GizmoImguiBegin(get_name(), flags); ensure_panel_icons(); process_uv_commands(); // clicks from the UV editor pane, run here where the GL context is current + // After the pane's commands, which can close it or change the view, and before the View row, so the row + // lights the view this frame ends up in and a click on it has the last word. + follow_island_highlight(); const float previous_body_h = m_panel_body_h; const float previous_footer_h = m_panel_footer_h; @@ -6281,7 +6326,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float if (v->is_model_part()) { ++idx; m_triangle_selectors[idx]->reset(); - m_triangle_selectors[idx]->request_update_render_data(); + m_triangle_selectors[idx]->request_update_render_data(true); } update_model_object(); m_parent.set_as_dirty(); @@ -6323,10 +6368,9 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float // ---- View: Normal / Fast / Checker / Distortion as one group, Wireframe on its own ---- // The underlying state stays m_use_shaded_preview + m_uv_check_mode. { - const int cur_mode = m_use_shaded_preview ? 1 : - m_uv_check_mode == UVCheckMode::Checker ? 2 : - m_uv_check_mode == UVCheckMode::Distortion ? 3 : 0; + const int cur_mode = view_mode(); int new_mode = cur_mode; + int picked = -1; // the button clicked, the active one included bool wf_toggle = false; bool open_uv_editor = false; // Checker and Distortion both draw *the unwrap* - the first the texture grid laid over it, the second @@ -6349,19 +6393,19 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + m_imgui->scaled(2.6f))); if (icon_toggle(701, "texture_displacement_real_preview.svg", cur_mode == 0, icon_md, _L("Normal"), _L("Normal - the real geometry, exactly what Bake will produce. Slower to update"))) - new_mode = 0; + new_mode = picked = 0; ImGui::SameLine(0.f, gap_s); if (icon_toggle(702, "texture_displacement_fast_preview.svg", cur_mode == 1, icon_md, _L("Fast"), _L("Fast - the relief is only shaded on, not built, and only for the layer you are " "editing. Updates instantly while you paint"))) - new_mode = 1; + new_mode = picked = 1; ImGui::SameLine(0.f, gap_s); if (icon_toggle(703, "texture_displacement_checker.svg", cur_mode == 2, icon_md, _L("Checker"), _L("Checker - a test grid instead of the texture. Where the squares stay square the " "texture is undistorted; where they stretch, it will too. Opens the UV editor if " "it is closed"), uv_view_na)) { - new_mode = 2; + new_mode = picked = 2; open_uv_editor = true; } ImGui::SameLine(0.f, gap_s); @@ -6369,7 +6413,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float _L("Distortion - blue-to-red stretch heatmap over the unwrap. Opens the UV editor if " "it is closed"), uv_view_na)) { - new_mode = 3; + new_mode = picked = 3; open_uv_editor = true; } vsep(icon_md); @@ -6393,6 +6437,8 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float if (new_mode == cur_mode) update_uv_editor(); // otherwise apply_view_mode() below does it } + if (picked >= 0) + m_island_view.picked(picked); if (new_mode != cur_mode) apply_view_mode(new_mode); if (wf_toggle) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp index a520fba426..5b754edfe9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp @@ -197,6 +197,27 @@ public: }; static PaintedColors painted_colors(const TriangleMesh &mesh, const TriangleSelector::TriangleSplittingData &paint); + // The view while the UV editor's selected islands are highlighted on the model. The highlight lies on the + // undisplaced surface, so the Normal view's relief hides it wherever it rises: Normal gives way to Fast + // while islands are selected and comes back once none are. A view the user clicks in the View row is + // theirs - the pane's background buttons are not a pick, Height only meaning "the texture view". + struct IslandViewFollow + { + bool shown = false; // the highlight, as of the last step() + bool switched = false; // Fast was switched on for the highlight, so Normal comes back once it is gone + bool keep_normal = false; // the user picked Normal in the View row while the highlight was shown + // The view to apply this frame (0 Normal, 1 Fast), or -1 for none. `view` is the one on screen, numbered as + // apply_view_mode() takes it; `debug_open` a debug stage on screen, which holds Normal itself. + int step(bool shown_now, int view, bool debug_open); + // A View row button clicked, the active one included. The row is drawn after step() in the same frame, + // so `shown` is this frame's. + void picked(int view) + { + switched = false; + keep_normal = shown && view == 0; + } + }; + void render_painter_gizmo() override; // Intercepts mouse input while "Adjust Texture" mode is on (dragging the on-canvas offset/ @@ -513,6 +534,11 @@ private: // The panel's view modes: 0 Normal, 1 Fast, 2 Checker, 3 Distortion. Shared by the View row and the pane's // background buttons, so both switch views the same way. void apply_view_mode(int mode); + // The view on screen, numbered as apply_view_mode() takes it. + int view_mode() const; + // Run once a frame, after the pane's commands: switches the view as m_island_view says. + void follow_island_highlight(); + IslandViewFollow m_island_view; // The pane header's thumbnail of the active layer's texture, rebuilt only when its image changes. static constexpr int UV_THUMB_PX = 64; const void *m_uv_thumb_source = nullptr; @@ -744,6 +770,8 @@ private: // in place. Rebuilt whenever the pane's selection differs from the one it was built for. GLModel m_island_overlay_glmodel; std::vector m_island_overlay_selection; + // The pane's selection while the overlay applies - the pane open over an unwrapped LSCM layer - else null. + const std::vector *island_highlight_selection(); void rebuild_island_overlay(const std::vector &selection); void render_island_overlay(); // Set on every paint event, cleared when the overlay is rebuilt in render_painter_gizmo(). Kept @@ -924,25 +952,26 @@ private: bool m_preview_job_pending = false; void queue_preview_job(); - // Per-slot GPU thumbnail cache for the layer list panel, keyed by the image_data pointer that - // was current the last time each thumbnail was built (see get_layer_thumbnail()). - std::array, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails; - std::array m_thumbnail_source{}; + // Per-slot GPU thumbnail cache for the layer list panel, each keyed by the image_data it was built from + // (see get_layer_thumbnail()). Held weakly rather than by address, as decode_height_texture()'s cache is: + // an image freed and replaced by another at the same address must not pass for it. + std::array, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails; + std::array>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_source; // The smoothing each cached thumbnail was built at, so a smoothing change re-uploads it. - std::array m_thumbnail_smoothing{}; + std::array m_thumbnail_smoothing{}; // Full-resolution height texture for the preview shader, keyed the same way (see // get_layer_height_texture()). A smoothing change re-uploads it, so the fast preview shows the // blur the bake will apply. - std::unique_ptr m_height_tex; - const void *m_height_tex_source = nullptr; - float m_height_tex_smoothing = -1.f; + std::unique_ptr m_height_tex; + std::weak_ptr> m_height_tex_source; + float m_height_tex_smoothing = -1.f; // The same, for the layer's *colour*, which the fast preview quantizes per fragment so it shows // the image at texel resolution rather than at the mesh's. Null for a grayscale texture. - std::unique_ptr m_color_tex; - const void *m_color_tex_source = nullptr; - float m_color_tex_smoothing = -1.f; + std::unique_ptr m_color_tex; + std::weak_ptr> m_color_tex_source; + float m_color_tex_smoothing = -1.f; // Library textures the picker has shown at least once, keyed by file path (see LibraryTexture). std::map m_library_textures; diff --git a/src/slic3r/GUI/UVEditorCanvas.cpp b/src/slic3r/GUI/UVEditorCanvas.cpp index 2432026689..a4a9901822 100644 --- a/src/slic3r/GUI/UVEditorCanvas.cpp +++ b/src/slic3r/GUI/UVEditorCanvas.cpp @@ -939,7 +939,8 @@ void UVEditorCanvas::on_mouse(wxMouseEvent &evt) m_active_edge = (hit.first >= 0 && is_edge_selected(hit)) ? hit : std::pair{ -1, -1 }; m_gesture = (m_active_edge.first >= 0) ? Gesture::MoveEdge : Gesture::Pan; } else { - const int hit = island_at(uv); + const std::vector selection_before = m_selection; + const int hit = island_at(uv); if (hit >= 0) { if (evt.ShiftDown()) { // Shift adds to the selection (and makes the clicked one the new primary). @@ -969,6 +970,10 @@ void UVEditorCanvas::on_mouse(wxMouseEvent &evt) m_selected_island = -1; } m_gesture = (m_selected_island >= 0) ? Gesture::MoveIsland : Gesture::Pan; + // The owner shows the selection on the model and would not hear of this click otherwise in time: a + // click that clears the selection starts no gesture, and one that sets it reports only on release. + if (m_selection != selection_before) + run_command(Command::SelectionChanged); } grab_mouse(); Refresh(); diff --git a/src/slic3r/GUI/UVEditorCanvas.hpp b/src/slic3r/GUI/UVEditorCanvas.hpp index 2fc1055cb8..a01d058983 100644 --- a/src/slic3r/GUI/UVEditorCanvas.hpp +++ b/src/slic3r/GUI/UVEditorCanvas.hpp @@ -108,10 +108,12 @@ public: // the command callback - the canvas has the selection and the view, the gizmo has the layer. // `value` carries the new setting for the Set* commands (an angle, a flag as 0/1, or an index into // SelectMode / Background) and is 0 otherwise. + // SelectionChanged is sent by the canvas itself when a click changes the island selection. It carries + // nothing: the owner reads selected_islands() back, and only needs telling that it should look. enum class Command { FrameAll, ToggleSnap, AverageScale, CutSelectedIsland, ProjectFromView, JoinSelected, UnjoinSelected, Unwrap, SetSeamAngle, SetConnectIslands, SetSelectMode, SetMarkSeams, SetSeamPath, ClearSeams, ClearUVEdits, - SetBackground, PickTexture, PaneClosed + SetBackground, PickTexture, PaneClosed, SelectionChanged }; void run_command(Command cmd, float value = 0.f); using CommandFn = std::function; diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt index db56a510ac..2fe6512d9e 100644 --- a/tests/slic3rutils/CMakeLists.txt +++ b/tests/slic3rutils/CMakeLists.txt @@ -37,6 +37,7 @@ add_executable(${_TEST_NAME}_tests test_plugin_json_depth.cpp test_shortcuts.cpp test_texture_color_palette.cpp + test_texture_island_view.cpp test_file_url.cpp test_user_manager.cpp ../fff_print/test_helpers.cpp diff --git a/tests/slic3rutils/test_texture_island_view.cpp b/tests/slic3rutils/test_texture_island_view.cpp new file mode 100644 index 0000000000..9655c86115 --- /dev/null +++ b/tests/slic3rutils/test_texture_island_view.cpp @@ -0,0 +1,99 @@ +// How the texture displacement gizmo's view follows the UV editor's island highlight. The rules live in +// libslic3r_gui; this is the suite that links it. Same Windows include prologue as +// test_texture_color_palette.cpp (wx pulls in ; keep WIN32_LEAN_AND_MEAN / NOMINMAX ahead of it). +#ifdef WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include +#endif + +#include +#include + +#include "slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp" + +using Follow = Slic3r::GUI::GLGizmoTextureDisplacement::IslandViewFollow; + +namespace { + +// The View row's indices, and step()'s "apply nothing". +constexpr int NORMAL = 0, FAST = 1, CHECKER = 2, DISTORTION = 3, NONE = -1; + +} // namespace + +TEST_CASE("a Normal view gives way to Fast while islands are selected and comes back once none are", "[TextureIslandView][TextureDisplacement]") +{ + Follow f; + CHECK(f.step(false, NORMAL, false) == NONE); + CHECK(f.step(true, NORMAL, false) == FAST); + CHECK(f.step(true, FAST, false) == NONE); + CHECK(f.step(false, FAST, false) == NORMAL); + CHECK(f.step(false, NORMAL, false) == NONE); +} + +TEST_CASE("a Fast, Checker or Distortion view is left alone by the island highlight", "[TextureIslandView][TextureDisplacement]") +{ + const int view = GENERATE(FAST, CHECKER, DISTORTION); + Follow f; + CHECK(f.step(true, view, false) == NONE); + CHECK(f.step(false, view, false) == NONE); +} + +TEST_CASE("arriving in Normal with islands already selected switches to Fast", "[TextureIslandView][TextureDisplacement]") +{ + // The pane's Height button over Checker lands in Normal with the islands still selected. + Follow f; + CHECK(f.step(true, CHECKER, false) == NONE); + CHECK(f.step(true, NORMAL, false) == FAST); + CHECK(f.step(false, FAST, false) == NORMAL); +} + +TEST_CASE("Normal picked while islands are selected stays until they are deselected", "[TextureIslandView][TextureDisplacement]") +{ + Follow f; + REQUIRE(f.step(true, NORMAL, false) == FAST); + f.picked(NORMAL); + CHECK(f.step(true, NORMAL, false) == NONE); + // A debug stage opening and closing over it changes nothing. + CHECK(f.step(true, NORMAL, true) == NONE); + CHECK(f.step(true, NORMAL, false) == NONE); + CHECK(f.step(false, NORMAL, false) == NONE); + CHECK(f.step(true, NORMAL, false) == FAST); +} + +TEST_CASE("Fast picked while it was switched on stays after deselecting", "[TextureIslandView][TextureDisplacement]") +{ + Follow f; + REQUIRE(f.step(true, NORMAL, false) == FAST); + f.picked(FAST); + CHECK(f.step(false, FAST, false) == NONE); +} + +TEST_CASE("Normal picked with nothing selected does not hold off the next switch", "[TextureIslandView][TextureDisplacement]") +{ + Follow f; + CHECK(f.step(false, NORMAL, false) == NONE); + f.picked(NORMAL); + CHECK(f.step(true, NORMAL, false) == FAST); +} + +TEST_CASE("a debug stage holds Normal while it is open, and the switch follows once it closes", "[TextureIslandView][TextureDisplacement]") +{ + Follow f; + REQUIRE(f.step(true, NORMAL, false) == FAST); + // The stage takes Normal itself, with the islands still selected. + CHECK(f.step(true, NORMAL, true) == NONE); + CHECK(f.step(true, NORMAL, false) == FAST); +} + +TEST_CASE("islands deselected while a debug stage is open apply no view over it", "[TextureIslandView][TextureDisplacement]") +{ + Follow f; + REQUIRE(f.step(true, NORMAL, false) == FAST); + CHECK(f.step(false, NORMAL, true) == NONE); + CHECK(f.step(false, NORMAL, false) == NONE); +}