mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-14 11:21:18 +00:00
Texture displacement: tooltip timing on a real clock, and a hover delay
The animations crawled when left alone and raced while the mouse moved. They were timed by ImGui::GetTime(), and nothing in OrcaSlicer ever sets io.DeltaTime, so it keeps imgui's default of 1/60 per frame: imgui's clock counts frames, not seconds. On a canvas that renders only when something asks it to, that is not a clock. Playback now reads a steady_clock, so a frame is due when it is due whatever the canvas is doing. An animation also restarts when its tooltip opens again, not only when a different one opens: a gap in the frames it was drawn on is what that looks like from here. Tooltips now wait for the pointer to rest on a control for 0.45 s. The panel is dense and a tooltip lands on the model beside it, so with no wait a sweep from the top of the panel down to Bake flashed a dozen large panels over the scene. After one has just been up the wait drops to 0.1 s for 0.7 s, so reading several settings in a row stays quick - but not to zero, or that same sweep would flash again once one tooltip had been seen. A pointer resting on a control produces no events, so the wait asks for the frame it will appear on itself.
This commit is contained in:
@@ -42,6 +42,17 @@ A file that is missing or will not decode is skipped, and a tooltip whose pictur
|
||||
exactly the text tooltip it was before. So the art for a tooltip can land after the tooltip does, and
|
||||
a half-finished set never breaks the panel.
|
||||
|
||||
## When a tooltip appears
|
||||
|
||||
A tooltip waits for the pointer to rest on its control for 0.45 s. The panel is dense and the tooltip
|
||||
lands on the model beside it, so with no wait a sweep from the top of the panel down to Bake flashes a
|
||||
dozen large panels over the scene. Once one has just been up, the wait drops to 0.1 s for 0.7 s
|
||||
afterwards, so reading several settings in a row stays quick - but not to zero, or that same sweep
|
||||
would flash again the moment one tooltip had been seen.
|
||||
|
||||
A pointer resting on a control produces no events, and the canvas only renders when something asks it
|
||||
to, so the wait itself has to keep asking; see panel_tooltip().
|
||||
|
||||
## Decoding and playback
|
||||
|
||||
`slic3r/GUI/AnimatedTexture.hpp` decodes a file once and uploads one GL texture per frame. Stills go
|
||||
@@ -57,14 +68,19 @@ The animation restarts when its tooltip opens, rather than running off a free-ru
|
||||
short loops of one setting going from one value to another, and joining one halfway through shows the
|
||||
end of a change the user never saw the start of.
|
||||
|
||||
Driving the frames takes two calls, not one. `GLCanvas3D::schedule_extra_frame()` only guarantees an
|
||||
idle event at the moment the next frame is due; `GLCanvas3D::on_idle()` returns without rendering
|
||||
Two things drive the frames, and both are needed. `GLCanvas3D::schedule_extra_frame()` only guarantees
|
||||
an idle event at the moment the next frame is due; `GLCanvas3D::on_idle()` returns without rendering
|
||||
anything unless something has marked the canvas dirty by then, so an animation left to the timer alone
|
||||
advances one frame per mouse movement and stands still whenever the mouse does. `set_dirty()` on the
|
||||
gizmo is what on_idle reads, through `GLGizmoBase::update_items_state()`, to find out there are frames
|
||||
still to draw. The pair of them is what makes an animation play at its own speed and stop costing
|
||||
anything the moment its tooltip closes.
|
||||
|
||||
Which frame is due is measured on a real clock, not `ImGui::GetTime()`. Nothing in OrcaSlicer ever sets
|
||||
`io.DeltaTime`, so it keeps imgui's default of 1/60 per frame and imgui's clock counts frames rather
|
||||
than seconds. On a canvas that renders only when asked, that is not a clock at all: an animation driven
|
||||
by it crawls while the canvas is idle and races while a moving mouse is rendering it flat out.
|
||||
|
||||
## Making one
|
||||
|
||||
`scripts/gen_texture_displacement_tips.py` draws the lot, in four languages, picked by what the
|
||||
|
||||
@@ -3701,6 +3701,28 @@ void GLGizmoTextureDisplacement::ensure_panel_icons()
|
||||
m_panel_icon_map[names[i]] = icons[i];
|
||||
}
|
||||
|
||||
// Seconds on a real clock, for the tooltips' own timing.
|
||||
//
|
||||
// Not ImGui::GetTime(): nothing in OrcaSlicer ever sets io.DeltaTime, so it keeps imgui's default of
|
||||
// 1/60 per frame and its clock counts frames rather than seconds. On a canvas that renders only when
|
||||
// something asks it to, that is not a clock at all - an animation driven by it crawled while the canvas
|
||||
// was idle and raced while a moving mouse was rendering it flat out.
|
||||
static double tip_now()
|
||||
{
|
||||
using clock = std::chrono::steady_clock;
|
||||
static const clock::time_point base = clock::now();
|
||||
return std::chrono::duration<double>(clock::now() - base).count();
|
||||
}
|
||||
|
||||
// How long the pointer has to rest on a control before its tooltip appears. The panel is dense and the
|
||||
// tooltip lands on the model beside it, so with no wait at all a sweep from the top of the panel down to
|
||||
// Bake flashes a dozen large panels over the scene. Once one has just been up the wait drops to almost
|
||||
// nothing, so reading several settings in a row stays quick - but not to nothing, or that same sweep
|
||||
// would flash again the moment one tooltip had been seen.
|
||||
static constexpr double TIP_DELAY = 0.45;
|
||||
static constexpr double TIP_DELAY_AGAIN = 0.10;
|
||||
static constexpr double TIP_STICKY = 0.70;
|
||||
|
||||
void GLGizmoTextureDisplacement::panel_tooltip(const wxString &text, const std::vector<TipMedia> &media)
|
||||
{
|
||||
panel_tooltip(into_u8(text), media);
|
||||
@@ -3708,6 +3730,23 @@ void GLGizmoTextureDisplacement::panel_tooltip(const wxString &text, const std::
|
||||
|
||||
void GLGizmoTextureDisplacement::panel_tooltip(const std::string &text, const std::vector<TipMedia> &media)
|
||||
{
|
||||
// The item's position stands in for its identity: moving to another control restarts the wait, and
|
||||
// staying on this one carries it on.
|
||||
const double now = tip_now();
|
||||
const ImVec2 item = ImGui::GetItemRectMin();
|
||||
if (item.x != m_tip_item.x || item.y != m_tip_item.y) {
|
||||
m_tip_item = item;
|
||||
m_tip_hover_since = now;
|
||||
}
|
||||
if (now - m_tip_hover_since < (now - m_tip_last_shown < TIP_STICKY ? TIP_DELAY_AGAIN : TIP_DELAY)) {
|
||||
// A pointer resting on a control produces no events, so nothing would redraw the canvas and the
|
||||
// tooltip would never get its chance to appear. Ask for the frame it appears on.
|
||||
set_dirty();
|
||||
m_parent.schedule_extra_frame(40);
|
||||
return;
|
||||
}
|
||||
m_tip_last_shown = now;
|
||||
|
||||
const ImGuiStyle &style = ImGui::GetStyle();
|
||||
const float gap = m_imgui->scaled(0.5f);
|
||||
const float wrap = m_imgui->scaled(20.f);
|
||||
@@ -3757,11 +3796,15 @@ void GLGizmoTextureDisplacement::render_tip_media(const std::vector<TipMedia> &m
|
||||
// Each animation plays from its first frame when its tooltip opens: these are short loops showing
|
||||
// one setting going from one value to another, and joining one halfway through shows the user the
|
||||
// end of a change they never saw the start of.
|
||||
if (key != m_tip_media_key) {
|
||||
// Restart on a different tooltip, and on the same one opening again - which is what a gap in the
|
||||
// frames it was drawn on means, since it is drawn on every frame for as long as it is on screen.
|
||||
const int this_frame = ImGui::GetFrameCount();
|
||||
if (key != m_tip_media_key || this_frame != m_tip_media_frame + 1) {
|
||||
m_tip_media_key = key;
|
||||
m_tip_media_start = ImGui::GetTime();
|
||||
m_tip_media_start = tip_now();
|
||||
}
|
||||
const double time = ImGui::GetTime() - m_tip_media_start;
|
||||
m_tip_media_frame = this_frame;
|
||||
const double time = tip_now() - m_tip_media_start;
|
||||
|
||||
const ImGuiStyle &style = ImGui::GetStyle();
|
||||
const float spacing = style.ItemSpacing.x;
|
||||
|
||||
@@ -1059,7 +1059,13 @@ private:
|
||||
// from its first frame each time its tooltip opens, rather than from wherever a free-running clock
|
||||
// happened to be.
|
||||
std::string m_tip_media_key;
|
||||
double m_tip_media_start = 0.;
|
||||
double m_tip_media_start = 0.;
|
||||
int m_tip_media_frame = -2; // the frame it was last drawn on, to spot the tooltip reopening
|
||||
// How long the pointer has rested on the control a tooltip belongs to, and when one was last on
|
||||
// screen, both on the real clock tip_now() keeps (see the .cpp for why ImGui's own will not do).
|
||||
ImVec2 m_tip_item{-1.f, -1.f};
|
||||
double m_tip_hover_since = 0.;
|
||||
double m_tip_last_shown = -1e9;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
Reference in New Issue
Block a user