mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-15 03:41:28 +00:00
Texture displacement: no tooltip while a control is being used, and a wait
that finishes on its own The hover delay only finished while the mouse was moving: a pointer resting on a control produces no events, so nothing redrew the canvas and the wait never reached its end - the tooltip appeared on the next pixel of movement instead. It asked for its frames with schedule_extra_frame(), which only arranges a timer whose event an idle event is assumed to follow, and on_idle renders nothing unless the canvas was already dirty when it ran. The wait and the animations now both use request_extra_frame(), which ends on_idle with evt.RequestMore() and so obliges wx to deliver the next idle; it is what 3DBed's fade uses. Asking for frames as fast as they come is safe now that which frame is drawn comes from the clock rather than from the frame count - a redraw that arrives early draws the same frame again. Nothing explains a control while the control is being used, so no tooltip appears while a slider is being dragged, a value typed into one, a combo open or a layer card dragged. The pointer sits on the very thing being edited, so a tooltip there covered the model exactly while the user was watching it change. The full wait comes back afterwards rather than the short one: having just let go of a slider is not a reason to be shown a picture of it. AnimatedTexture::texture_id() loses its time-to-next-frame out-parameter, which nothing asks for any more.
This commit is contained in:
@@ -50,8 +50,20 @@ dozen large panels over the scene. Once one has just been up, the wait drops to
|
||||
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.
|
||||
|
||||
No tooltip appears at all while a control is being used - a slider dragged, a value typed into one, a
|
||||
combo open, a layer card being dragged. The pointer sits on the very thing being edited, so a tooltip
|
||||
there covers the model exactly while the user is watching it change. The full wait comes back
|
||||
afterwards rather than the short one: having just let go of a slider is not a reason to be shown a
|
||||
picture of it.
|
||||
|
||||
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().
|
||||
to, so the wait has to ask for its own frames, and so does an animation once one is on screen. Both use
|
||||
`GLCanvas3D::request_extra_frame()`, which ends `on_idle()` with `evt.RequestMore()` and so obliges wx
|
||||
to deliver the next idle. `schedule_extra_frame()` is not enough on its own: it only arranges a timer
|
||||
whose event an idle event is assumed to follow, and on_idle renders nothing unless the canvas was
|
||||
already dirty when it ran. Asking for frames as fast as they come is safe here because which frame is
|
||||
drawn comes from the clock, not from the frame count, so a redraw that arrives early simply draws the
|
||||
same frame again.
|
||||
|
||||
## Decoding and playback
|
||||
|
||||
@@ -68,15 +80,7 @@ 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.
|
||||
|
||||
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
|
||||
Which frame is drawn 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.
|
||||
|
||||
@@ -207,26 +207,18 @@ void AnimatedTexture::reset()
|
||||
m_duration = 0.;
|
||||
}
|
||||
|
||||
unsigned int AnimatedTexture::texture_id(double time, double *time_to_next) const
|
||||
unsigned int AnimatedTexture::texture_id(double time) const
|
||||
{
|
||||
if (m_frames.empty()) {
|
||||
if (time_to_next != nullptr)
|
||||
*time_to_next = 0.;
|
||||
if (m_frames.empty())
|
||||
return 0;
|
||||
}
|
||||
if (m_frames.size() == 1 || m_duration <= 0.) {
|
||||
if (time_to_next != nullptr)
|
||||
*time_to_next = 0.;
|
||||
if (m_frames.size() == 1 || m_duration <= 0.)
|
||||
return m_frames.front().texture->get_id();
|
||||
}
|
||||
|
||||
const double t = time - m_duration * std::floor(time / m_duration);
|
||||
// Frames are few; a linear walk beats keeping an index around and getting it stale.
|
||||
size_t i = 0;
|
||||
while (i + 1 < m_frames.size() && t >= m_frames[i].start + m_frames[i].delay)
|
||||
++i;
|
||||
if (time_to_next != nullptr)
|
||||
*time_to_next = std::max(0., m_frames[i].start + m_frames[i].delay - t);
|
||||
return m_frames[i].texture->get_id();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,11 +39,8 @@ public:
|
||||
// Length of one loop, in seconds; 0 for a still image.
|
||||
double duration() const { return m_duration; }
|
||||
|
||||
// The texture to draw `time` seconds into the loop (which wraps), and - when `time_to_next` is
|
||||
// given - how much longer that frame stays up. A canvas that only redraws on demand uses the
|
||||
// latter to ask for the next redraw at the right moment instead of spinning at full speed.
|
||||
// 0, and a time_to_next of 0, when nothing is loaded.
|
||||
unsigned int texture_id(double time, double *time_to_next = nullptr) const;
|
||||
// The texture to draw `time` seconds into the loop, which wraps. 0 when nothing is loaded.
|
||||
unsigned int texture_id(double time) const;
|
||||
|
||||
private:
|
||||
struct Frame
|
||||
|
||||
@@ -3730,6 +3730,17 @@ void GLGizmoTextureDisplacement::panel_tooltip(const wxString &text, const std::
|
||||
|
||||
void GLGizmoTextureDisplacement::panel_tooltip(const std::string &text, const std::vector<TipMedia> &media)
|
||||
{
|
||||
// Nothing explains a control while the control is being used: dragging a slider, typing a value
|
||||
// into one, picking from a combo, dragging a layer card. The pointer sits on the very thing being
|
||||
// edited, so without this the tooltip covers the model exactly while the user is watching it change.
|
||||
// The full wait comes back afterwards rather than the short one - having just let go of a slider is
|
||||
// not a reason to be shown a picture of it.
|
||||
if (ImGui::IsAnyItemActive() || ImGui::IsAnyMouseDown()) {
|
||||
m_tip_item = ImVec2(-1.f, -1.f);
|
||||
m_tip_last_shown = -1e9;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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();
|
||||
@@ -3739,10 +3750,16 @@ void GLGizmoTextureDisplacement::panel_tooltip(const std::string &text, const st
|
||||
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);
|
||||
// A pointer resting on a control produces no events at all, so unless the wait asks for frames
|
||||
// itself nothing redraws the canvas and the wait never finishes - the tooltip then appears only
|
||||
// on the next pixel of mouse movement.
|
||||
//
|
||||
// request_extra_frame() and not schedule_extra_frame(): the latter only arranges a timer, whose
|
||||
// event an idle event is assumed to follow, and GLCanvas3D::on_idle() renders nothing unless the
|
||||
// canvas was already dirty when it ran. request_extra_frame() ends on_idle with
|
||||
// evt.RequestMore(), which obliges wx to deliver the next one. It is what 3DBed's fade uses.
|
||||
// Costing a few hundred milliseconds of frames is the cheaper half of that trade.
|
||||
m_parent.request_extra_frame();
|
||||
return;
|
||||
}
|
||||
m_tip_last_shown = now;
|
||||
@@ -3829,23 +3846,16 @@ void GLGizmoTextureDisplacement::render_tip_media(const std::vector<TipMedia> &m
|
||||
}
|
||||
|
||||
ImGui::BeginGroup();
|
||||
double next = 0.;
|
||||
const unsigned int id = texture->texture_id(time, &next);
|
||||
const unsigned int id = texture->texture_id(time);
|
||||
ImGui::SetCursorPosX(cell_x + std::round(0.5f * (cell_w - w)));
|
||||
ImGui::Image((ImTextureID) (intptr_t) id, ImVec2(w, h));
|
||||
ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), frame, style.FrameRounding);
|
||||
// The canvas only redraws on demand, so an animation has to ask for its own frames - and for
|
||||
// exactly when the next one is due, rather than for a redraw as fast as the machine will go.
|
||||
//
|
||||
// Both halves are needed. schedule_extra_frame() alone only guarantees an idle event at that
|
||||
// moment; GLCanvas3D::on_idle() returns without rendering anything unless something has marked
|
||||
// the canvas dirty by then, which is why an animation left to that alone advanced one frame per
|
||||
// mouse movement and stood still whenever the mouse did. set_dirty() is what on_idle reads
|
||||
// (through GLGizmoBase::update_items_state()) to find out this gizmo still has frames to draw.
|
||||
if (texture->is_animated()) {
|
||||
set_dirty();
|
||||
m_parent.schedule_extra_frame(int(next * 1000.0));
|
||||
}
|
||||
// The canvas only redraws on demand, so an animation has to ask for its own frames. It asks for
|
||||
// them as fast as they come rather than pacing itself with a timer: which frame is drawn is
|
||||
// decided by the clock above, so a redraw that arrives early simply draws the same frame again,
|
||||
// and a guaranteed frame is worth more here than a frugal one that may never arrive.
|
||||
if (texture->is_animated())
|
||||
m_parent.request_extra_frame();
|
||||
|
||||
if (!entry.caption.empty()) {
|
||||
// Centred under its own illustration, and never wider than its column.
|
||||
|
||||
Reference in New Issue
Block a user