17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-07 12:13:17 +01:00

feat(editor): what the editor refuses shows by the mouse cursor

An error put in the status bar also shows for 4 s by the cursor, and the
refusals the tools kept in their panels (a curve which can't be laid,
a switch which doesn't fit, a signal which can't be moved, a drive
without the variant) go there as well.
This commit is contained in:
maj00r
2026-10-21 11:47:26 +02:00
parent 9d27fd1ace
commit da18883558
9 changed files with 70 additions and 27 deletions

View File

@@ -973,6 +973,12 @@ void editor_mode::undo_last()
}
}
std::string editor_mode::refused(std::string Text)
{
ui()->set_status(Text, true);
return Text;
}
void editor_mode::drop_step(std::size_t const Step)
{
if (Step == 0 || m_history.size() != Step)

View File

@@ -175,6 +175,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
// a single step of the history, see undo_last()
void undo_one();
void drop_step(std::size_t const Step);
std::string refused(std::string Text);
void redo_one();
// the changes of the heightmap terrain since the last step become a step of the history: a stroke of the brush,
// or what a frame did; the next change is recorded from now on
@@ -955,6 +956,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
m_route.result = alignment::compute(m_route.design);
}
void route_apply();
void route_lay();
bool route_relay_through_switches(editor_track::chain const &Chain, std::vector<std::pair<TTrack *, editor_track::state>> &States, std::vector<TTrack *> &Created, TTrack *&Last);
void route_recommend(alignment::vertex &Vertex) const;
void draw_route_overlay() const;

View File

@@ -573,6 +573,14 @@ bool editor_mode::route_from_line(TTrack &Track)
}
void editor_mode::route_apply()
{
m_route.error.clear();
route_lay();
if (false == m_route.error.empty())
refused(m_route.error);
}
void editor_mode::route_lay()
{
auto &route{m_route};
route.status.clear();

View File

@@ -586,7 +586,7 @@ void editor_mode::signal_release()
return;
if (false == tool.moved_valid)
{
tool.status = STR_C("Drop the signal by a track");
tool.status = refused(STR_C("Drop the signal by a track"));
return;
}
tool.moved_valid = false;
@@ -600,7 +600,7 @@ bool editor_mode::signal_move_to(signal_standing const &Standing, signal_spot co
auto const instance{Standing.include.instance};
if (false == scene::Layers.tracked(instance) || false == scene::Layers.removable(instance))
{
tool.status = STR_C("This signal is in a file the editor doesn't change, it can't be moved");
tool.status = refused(STR_C("This signal is in a file the editor doesn't change, it can't be moved"));
return false;
}
auto const &included{scene::Layers.instance(instance)};
@@ -659,7 +659,7 @@ bool editor_mode::signal_flip(scene::instance_handle const Instance)
signal_spot spot;
if (standing == nullptr || false == signal_standing_spot(*standing, spot))
{
m_signal.status = STR_C("No track by the signal to take it across");
m_signal.status = refused(STR_C("No track by the signal to take it across"));
return false;
}
auto const across{plan_of(spot.position) - plan_of(spot.axis)};
@@ -675,7 +675,7 @@ bool editor_mode::signal_turn(scene::instance_handle const Instance)
signal_spot spot;
if (standing == nullptr || false == signal_standing_spot(*standing, spot))
{
m_signal.status = STR_C("No track by the signal to turn it to");
m_signal.status = refused(STR_C("No track by the signal to turn it to"));
return false;
}
auto const *item{signal_template_of(standing->include.file)};
@@ -730,7 +730,7 @@ bool editor_mode::signal_place(signal_spot const &Spot, signal_template const &T
std::string name{tool.name};
if (name.empty() || name.find_first_of(" \t\";") != std::string::npos)
{
tool.status = STR_C("Give the signal a name without spaces first");
tool.status = refused(STR_C("Give the signal a name without spaces first"));
return false;
}
signal_refresh(true);
@@ -753,7 +753,7 @@ bool editor_mode::signal_place(signal_spot const &Spot, signal_template const &T
std::string error;
if (place_templates({item}, error) == 0)
{
tool.status = STR_C("The signal couldn't be placed: ") + error;
tool.status = refused(STR_C("The signal couldn't be placed: ") + error);
return false;
}
auto const instance{m_history.back().instances.front()};

View File

@@ -570,7 +570,7 @@ void editor_mode::straights_apply(std::vector<editor_track::straight> const &Lin
return false == finite(End.first) || false == finite(End.second) || false == (glm::distance(End.first, End.second) >= 0.1 && glm::distance(End.first, End.second) <= 100000.0);
}))
{
state.status = STR_C("The ends have to be numbers, from 0.1 m to 100 km apart");
state.status = refused(STR_C("The ends have to be numbers, from 0.1 m to 100 km apart"));
return;
}
auto const tracks{editor_track::straight_affected(Lines)};
@@ -627,6 +627,7 @@ void editor_mode::render_straight_gizmo()
auto const tracks{state.fitting ? std::vector<TTrack *>{} : editor_track::straight_affected(lines)};
if (false == tracks_editable(tracks, state.status))
{
refused(state.status);
state.fitting = false;
return;
}
@@ -947,7 +948,7 @@ void editor_mode::straight_reshape(editor_track::straight const &Line, double co
editor_track::apply(*entry.first, entry.second);
editor_track::commit(tracks);
undo_split();
state.status = m_route.error;
state.status = refused(m_route.error);
return;
}
m_route.design.start_reserve = 0.0;
@@ -969,7 +970,7 @@ void editor_mode::straight_reshape(editor_track::straight const &Line, double co
editor_track::apply(*entry.first, entry.second);
editor_track::commit(tracks);
undo_split();
state.status = m_route.result.errors.empty() ? STR_C("The curve can't be fitted") : m_route.result.errors.front();
state.status = refused(m_route.result.errors.empty() ? STR_C("The curve can't be fitted") : m_route.result.errors.front());
return;
}
auto pieces{alignment::pieces(m_route.result, m_route.design, m_route.chain.tracks.size())};

View File

@@ -193,7 +193,7 @@ bool editor_mode::place_switch_drive(TTrack &Switch)
item.file = family.files[index];
if (item.file.empty())
{
tool.status = missing_drive_variant(tool.drive, spot->rightswitch, driveright);
tool.status = refused(missing_drive_variant(tool.drive, spot->rightswitch, driveright));
return false;
}
item.location = spot->location + glm::dvec3{0.0, family.heights[index], 0.0};
@@ -206,7 +206,7 @@ bool editor_mode::place_switch_drive(TTrack &Switch)
std::string error;
if (place_templates({item}, error) == 0)
{
tool.status = STR_C("The drive couldn't be placed: ") + error;
tool.status = refused(STR_C("The drive couldn't be placed: ") + error);
return false;
}
tool.status = format(STR_C("Switch %s with the drive %s: events %s+ and %s- switch it, also in the map"), Switch.name().c_str(), item.file.c_str(), Switch.name().c_str(), Switch.name().c_str());
@@ -253,7 +253,7 @@ bool editor_mode::replace_switch_drive(TTrack &Switch, int const Family, bool co
auto const present{find_switch_drive(Switch)};
if (false == present.has_value())
{
tool.status = format(STR_C("No drive of the switch %s among the described drives"), Switch.name().c_str());
tool.status = refused(format(STR_C("No drive of the switch %s among the described drives"), Switch.name().c_str()));
return false;
}
auto const &found{present->standing};
@@ -263,7 +263,7 @@ bool editor_mode::replace_switch_drive(TTrack &Switch, int const Family, bool co
item.file = tool.drives[family].files[index];
if (item.file.empty())
{
tool.status = Flip ? format(STR_C("The drive %s has no variant with the drive on the other side"), tool.drives[family].name.c_str()) : missing_drive_variant(family, (index & 2) != 0, (index & 1) != 0);
tool.status = refused(Flip ? format(STR_C("The drive %s has no variant with the drive on the other side"), tool.drives[family].name.c_str()) : missing_drive_variant(family, (index & 2) != 0, (index & 1) != 0));
return false;
}
if (drive_file(item.file) == found.file)
@@ -276,7 +276,7 @@ bool editor_mode::replace_switch_drive(TTrack &Switch, int const Family, bool co
item.tilt = {angle_value(found.values, 5), angle_value(found.values, 6)};
if (remove_templates({found.instance}) == 0)
{
tool.status = STR_C("The drive of this switch can't be changed in its file");
tool.status = refused(STR_C("The drive of this switch can't be changed in its file"));
return false;
}
std::string error;
@@ -284,7 +284,7 @@ bool editor_mode::replace_switch_drive(TTrack &Switch, int const Family, bool co
{
scene::Layers.removed(found.instance, false);
m_history.pop_back();
tool.status = STR_C("The drive couldn't be placed: ") + error;
tool.status = refused(STR_C("The drive couldn't be placed: ") + error);
return false;
}
join_last_steps();

View File

@@ -88,7 +88,7 @@ bool editor_mode::start_switch_placement()
auto *track{selected};
if (tool.templates[tool.armed].double_slip)
{
tool.status = STR_C("A double slip goes only into a straight crossing another one");
tool.status = refused(STR_C("A double slip goes only into a straight crossing another one"));
return false;
}
editor_track::curve curve;
@@ -96,12 +96,12 @@ bool editor_mode::start_switch_placement()
std::string error;
if (false == editor_track::find_curve(*track, m_straights.tolerance, m_route.design.norms.gauge, curve))
{
tool.status = STR_C("The selected track is neither a recognized straight nor a curve");
tool.status = refused(STR_C("The selected track is neither a recognized straight nor a curve"));
return false;
}
if (false == editor_track::find_chain(curve.from, curve.to, chain, error, true))
{
tool.status = error.empty() ? std::string{STR_C("The curve can't be followed")} : error;
tool.status = refused(error.empty() ? std::string{STR_C("The curve can't be followed")} : error);
return false;
}
for (auto const &part : editor_track::chain_parts(chain))
@@ -305,7 +305,7 @@ bool editor_mode::cut_straight(editor_track::straight const &Line, double const
std::string reason;
if (false == tracks_editable(Line.tracks, reason))
{
m_switch.status = reason;
m_switch.status = refused(reason);
ui()->set_status(reason, true);
return false;
}
@@ -393,7 +393,7 @@ bool editor_mode::place_switch_on_straight(editor_track::straight const &Line, e
auto const &shape{Shape};
if (shape.length > Line.length - 0.02)
{
m_switch.status = format(STR_C("The straight is %.1f m long, too short for the %.1f m switch"), Line.length, shape.length);
m_switch.status = refused(format(STR_C("The straight is %.1f m long, too short for the %.1f m switch"), Line.length, shape.length));
return false;
}
auto const from{switch_from(Line, shape.length, Along, Direction, Snap)};
@@ -402,7 +402,7 @@ bool editor_mode::place_switch_on_straight(editor_track::straight const &Line, e
TTrack *first{nullptr};
if (false == cut_straight(Line, from, to, States, Created, Removed, &first))
{
m_switch.status = STR_C("The switch would overlap another switch, move it onto plain track");
m_switch.status = refused(STR_C("The switch would overlap another switch, move it onto plain track"));
return false;
}
auto const point = [&](double const Distance) { return Line.start + glm::dvec3{Line.direction.x, Line.grade, Line.direction.y} * Distance; };
@@ -426,7 +426,7 @@ std::vector<TTrack *> editor_mode::create_switch_set(editor_track::switch_templa
auto *link{two != nullptr ? editor_track::create_path(Style, Paths[4]) : nullptr};
if (link == nullptr)
{
m_switch.status = STR_C("The switch couldn't be created from this template");
m_switch.status = refused(STR_C("The switch couldn't be created from this template"));
return {};
}
editor_track::commit({one, two, link});
@@ -437,7 +437,7 @@ std::vector<TTrack *> editor_mode::create_switch_set(editor_track::switch_templa
auto *track{editor_track::create_switch(Shape, Paths, Style, Name)};
if (track == nullptr)
{
m_switch.status = STR_C("The switch couldn't be created from this template");
m_switch.status = refused(STR_C("The switch couldn't be created from this template"));
return {};
}
editor_track::commit({track});
@@ -974,7 +974,7 @@ bool editor_mode::insert_double_slip(editor_track::straight const &Line, glm::dv
slip_plan plan;
if (false == plan_double_slip(Line, Point, Shape, plan))
{
state.status = plan.problem;
state.status = refused(plan.problem);
return false;
}
if (plan.replaces != nullptr)
@@ -1041,7 +1041,7 @@ bool editor_mode::replace_double_slip(TTrack &Part, editor_track::switch_templat
switches[i] = simulation::Paths.find(base + "_" + static_cast<char>('a' + i));
if (switches[i] == nullptr || switches[i]->m_editorremoved || switches[i]->eType != tt_Switch || switches[i]->m_paths.size() < 2)
{
state.status = STR_C("The double slip isn't complete, it can't be replaced");
state.status = refused(STR_C("The double slip isn't complete, it can't be replaced"));
return false;
}
}
@@ -1077,7 +1077,7 @@ bool editor_mode::replace_double_slip(TTrack &Part, editor_track::switch_templat
partner = i;
if (partner < 0)
{
state.status = STR_C("The double slip geometry isn't recognized");
state.status = refused(STR_C("The double slip geometry isn't recognized"));
return false;
}
auto const &a{ends[0]};
@@ -1346,7 +1346,7 @@ void editor_mode::insert_curved_switch(int const Direction, int const Side)
auto const total{tool.frame.back().station};
if (shape.length > total - 0.02)
{
tool.status = format(STR_C("The curve is %.1f m long, too short for the %.1f m switch"), total, shape.length);
tool.status = refused(format(STR_C("The curve is %.1f m long, too short for the %.1f m switch"), total, shape.length));
return;
}
auto const station{std::clamp(tool.along, Direction > 0 ? 0.0 : shape.length, Direction > 0 ? total - shape.length : total)};

View File

@@ -646,6 +646,7 @@ void editor_ui::render_()
// strips of the unpinned docks, inside the toolbar and the status bar; the pins over the tab bars drawn by now
autohide_strips();
autohide_pins();
render_refusal();
}
// updates state of UI elements
@@ -1026,6 +1027,28 @@ void editor_ui::set_status(std::string const &Status, bool const Error)
{
m_layerspanel.status = Status;
m_layerspanel.status_error = Error;
if (Error && false == Status.empty())
{
m_refusal = Status;
m_refusaltime = ImGui::GetTime();
}
}
void editor_ui::render_refusal()
{
auto const age{ImGui::GetTime() - m_refusaltime};
if (m_refusal.empty() || age > 4.0)
return;
auto const &io{ImGui::GetIO()};
auto const alpha{static_cast<float>(std::clamp(4.0 - age, 0.0, 1.0))};
auto const wrap{std::round(ImGui::GetFontSize() * 30.0f)};
auto const size{ImGui::CalcTextSize(m_refusal.c_str(), nullptr, false, wrap)};
ImVec2 at{io.MousePos.x + 20.0f, io.MousePos.y - 24.0f - size.y};
at.x = std::clamp(at.x, 10.0f, std::max(10.0f, io.DisplaySize.x - size.x - 10.0f));
at.y = std::clamp(at.y, 10.0f, std::max(10.0f, io.DisplaySize.y - size.y - 10.0f));
auto *draw{ImGui::GetForegroundDrawList(ImGui::GetMainViewport())};
draw->AddRectFilled(ImVec2(at.x - 6.0f, at.y - 4.0f), ImVec2(at.x + size.x + 6.0f, at.y + size.y + 4.0f), IM_COL32(40, 0, 0, static_cast<int>(210 * alpha)), 4.0f);
draw->AddText(ImGui::GetFont(), ImGui::GetFontSize(), at, IM_COL32(255, 150, 130, static_cast<int>(255 * alpha)), m_refusal.c_str(), nullptr, wrap);
}
void editor_ui::render_rotation_controls()

View File

@@ -165,6 +165,7 @@ class editor_ui : public ui_layer
private:
// methods
void render_refusal();
void render_mode_options(nodebank_panel::edit_mode const Mode);
// windows of the editor drawn by the user interface itself
void render_tool_options();
@@ -208,6 +209,8 @@ class editor_ui : public ui_layer
std::function<void()> m_workspacebar;
std::function<void()> m_editmenu;
bool m_layoutreset{false}; // the default layout is to be built again on the next frame
std::string m_refusal;
double m_refusaltime{-100.0};
bool m_tooloptionsopen{true};
bool m_inspectoropen{true};
bool m_sceneopen{true};