Fix stale and hidden highlights in the texture displacement gizmo (#16409)

# Description

Fixes highlights in the texture displacement gizmo that stayed on screen
after the state behind them changed, or were drawn where they could not
be seen. Erase whole model, Select whole model and Capture current view
update the paint highlight at once instead of on the next right-click. A
stroke released outside the 3D view ends on the next mouse move, in
every paint gizmo, rather than leaving the brush stuck in its pressed
colour. The layer textures stop showing an image that has been replaced.
Selecting islands in the UV editor switches a Normal view to Fast, where
their highlight is visible instead of buried under the displaced relief,
and Normal comes back once no island is selected unless another view was
picked in the meantime.

No change to slicing output; the changes are confined to the paint
gizmos and the UV editor.

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
This commit is contained in:
SoftFever
2026-10-13 00:23:33 +08:00
committed by GitHub
7 changed files with 237 additions and 46 deletions
@@ -1036,6 +1036,11 @@ bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event)
Vec2d mouse_pos = mouse_coord.cast<double>();
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());
@@ -562,6 +562,11 @@ void GLGizmoTextureDisplacement::on_shutdown()
m_shaded_island_delta = Eigen::Matrix<float, 2, 3>::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<int> *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<int> *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<int> &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<int> *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<float, 12>
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<GLTexture> 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<GLTexture> 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) {
@@ -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<int> 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<int> *island_highlight_selection();
void rebuild_island_overlay(const std::vector<int> &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<std::unique_ptr<GLTexture>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails;
std::array<const void *, TEXTURE_DISPLACEMENT_MAX_LAYERS> 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<std::unique_ptr<GLTexture>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails;
std::array<std::weak_ptr<const std::vector<unsigned char>>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_source;
// The smoothing each cached thumbnail was built at, so a smoothing change re-uploads it.
std::array<float, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_smoothing{};
std::array<float, TEXTURE_DISPLACEMENT_MAX_LAYERS> 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<GLTexture> m_height_tex;
const void *m_height_tex_source = nullptr;
float m_height_tex_smoothing = -1.f;
std::unique_ptr<GLTexture> m_height_tex;
std::weak_ptr<const std::vector<unsigned char>> 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<GLTexture> m_color_tex;
const void *m_color_tex_source = nullptr;
float m_color_tex_smoothing = -1.f;
std::unique_ptr<GLTexture> m_color_tex;
std::weak_ptr<const std::vector<unsigned char>> 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<std::string, LibraryTexture> m_library_textures;
+6 -1
View File
@@ -939,7 +939,8 @@ void UVEditorCanvas::on_mouse(wxMouseEvent &evt)
m_active_edge = (hit.first >= 0 && is_edge_selected(hit)) ? hit : std::pair<int, int>{ -1, -1 };
m_gesture = (m_active_edge.first >= 0) ? Gesture::MoveEdge : Gesture::Pan;
} else {
const int hit = island_at(uv);
const std::vector<int> 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();
+3 -1
View File
@@ -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<void(Command, float)>;
+1
View File
@@ -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
@@ -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 <windows.h>; 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 <Windows.h>
#endif
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#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);
}