/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // over the 3d view: the tools of the field of work along its left edge, the modes of the gizmo at its top, the axes of the // world and the top view in its top right corner. the same tools in the toolbar under the menu, with their names #include "stdafx.h" #include "application/editormode.h" #include "application/editoruilayer.h" #include "editor/editorIcons.hpp" #include "editor/editorSettings.hpp" #include "rendering/renderer.h" #include "utilities/Globals.h" #include "utilities/translation.h" #include "imgui/imgui.h" #include #include #include #include #include struct editor_tool_button { editor_icons::icon picture; // icon::count if it has none: then it's only in the toolbar, as its name char const *label; char const *key; // nullptr if there's none char const *tooltip; // nullptr if the label says enough bool chosen; std::function action; bool group{false}; // a gap before it, it starts another group bool enabled{true}; }; namespace { using editor_icons::icon; using tool_button = editor_tool_button; float scale() { return std::max(1.0f, Global.ui_scale); } // side of a button, the icon with some room around it float button_side() { return editor_icons::size() + 10.0f * scale(); } // a window of no looks of its own, for the buttons over the 3d view; it takes the mouse only where it lies bool begin_overlay(char const *Name, ImVec2 const &Position, ImVec2 const &Size) { ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); ImGui::SetNextWindowPos(Position); ImGui::SetNextWindowSize(Size); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(1.0f, 1.0f)); auto const flags{ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoScrollWithMouse}; auto const open{ImGui::Begin(Name, nullptr, flags)}; ImGui::PopStyleVar(3); return open; } // the name of the tool with its key, and what it does void button_tooltip(tool_button const &Button) { std::string tip{Button.label}; if (Button.key != nullptr) tip += std::string{" ["} + Button.key + "]"; if (Button.tooltip != nullptr) tip += std::string{"\n"} + Button.tooltip; ImGui::SetTooltip("%s", tip.c_str()); } // the first letters of the label in place of the icon, if the renderer can't show images void icon_or_letters(ImDrawList *List, tool_button const &Button, ImVec2 const &Center, ImU32 const Color) { if (editor_icons::draw(List, Button.picture, Center, Color)) return; char const text[]{Button.label[0], (Button.label[0] != '\0' ? Button.label[1] : '\0'), '\0'}; auto const size{ImGui::CalcTextSize(text)}; List->AddText(ImVec2(Center.x - size.x * 0.5f, Center.y - size.y * 0.5f), Color, text); } // the button with the icon at the place; the label stands in for the icon if the renderer can't show images void icon_button(tool_button const &Button, ImVec2 const &Position, int const Id) { auto const side{button_side()}; ImGui::SetCursorScreenPos(Position); ImGui::PushID(Id); auto const clicked{ImGui::InvisibleButton("##tool", ImVec2(side, side))}; ImGui::PopID(); auto const hovered{ImGui::IsItemHovered()}; auto *list{ImGui::GetWindowDrawList()}; ImVec2 const end{Position.x + side, Position.y + side}; auto const rounding{4.0f * scale()}; auto const background{Button.chosen ? ImGui::GetColorU32(ImGuiCol_ButtonActive) : hovered && Button.enabled ? ImGui::GetColorU32(ImGuiCol_ButtonHovered) : IM_COL32(24, 26, 30, 200)}; list->AddRectFilled(Position, end, background, rounding); if (Button.chosen) list->AddRect(Position, end, ImGui::GetColorU32(ImGuiCol_CheckMark), rounding, 0, 1.5f * scale()); auto const color{Button.enabled ? (Button.chosen || hovered ? IM_COL32(255, 255, 255, 255) : IM_COL32(215, 220, 225, 235)) : IM_COL32(150, 150, 150, 110)}; icon_or_letters(list, Button, ImVec2(Position.x + side * 0.5f, Position.y + side * 0.5f), color); if (hovered) button_tooltip(Button); if (clicked && Button.enabled && Button.action) Button.action(); } } // namespace void editor_mode::render_viewport_overlays() { auto const width{editor_ui::view_max().x - editor_ui::view_min().x}; auto const height{editor_ui::view_max().y - editor_ui::view_min().y}; // a view squeezed between the docks has no room for them if (width < 8.0f * button_side() || height < 6.0f * button_side()) return; render_viewport_tools(); render_viewport_gizmo(); render_view_axes(); } void editor_mode::work_area_tools(std::vector &Buttons) { switch (current_work_area()) { case work_area::surroundings: { struct mode { icon picture; char const *label; char const *key; nodebank_panel::edit_mode which; }; mode const modes[] = {{icon::select, STR_C("Select"), "1", nodebank_panel::MODIFY}, {icon::insert, STR_C("Insert"), "2", nodebank_panel::ADD}, {icon::brush, STR_C("Brush"), "3", nodebank_panel::BRUSH}, {icon::area_fill, STR_C("Area fill"), "4", nodebank_panel::FILL}, {icon::copy_to_bank, STR_C("Copy to bank"), "5", nodebank_panel::COPY}}; for (auto const &entry : modes) Buttons.push_back({entry.picture, entry.label, entry.key, nullptr, ui()->mode() == entry.which, [this, which = entry.which]() { choose_edit_mode(which); }}); break; } case work_area::tracks: { auto const vehicle{m_track_tab == track_tab::lineside && m_vehicle.open}; auto const add = [&](track_tab const Tab, icon const Picture, bool const Group) { auto const &modes{track_modes()}; auto const found{std::find_if(modes.begin(), modes.end(), [&](track_mode const &Mode) { return Mode.tab == Tab; })}; if (found == modes.end()) return; // a tool chosen again gives way to the select one auto const chosen{(m_track_tab == Tab || (Tab == track_tab::straights && track_plan_tab())) && false == (Tab == track_tab::lineside && vehicle)}; Buttons.push_back({Picture, STR_C(found->label), found->key, STR_C(found->tooltip), chosen, [this, Tab, chosen]() { show_track_tab(chosen ? track_tab::path : Tab); }, Group}); }; add(track_tab::path, icon::select, false); add(track_tab::lay, icon::lay_track, false); add(track_tab::straights, icon::curve, false); add(track_tab::turnout, icon::turnout, false); add(track_tab::parallel, icon::parallel, false); add(track_tab::turntable, icon::turntable, false); add(track_tab::signals, icon::signal, true); add(track_tab::lineside, icon::objects, false); Buttons.push_back({icon::vehicle, STR_C("Trainset"), nullptr, STR_C("A trainset on the selected track: written to the scenery on save, and to drive at once"), vehicle, [this, vehicle]() { show_track_tab(vehicle ? track_tab::path : track_tab::lineside); m_vehicle.expand = false == vehicle; }}); auto const advanced{m_track_tab == track_tab::profile || m_track_tab == track_tab::speed || m_track_tab == track_tab::joints || m_track_tab == track_tab::infra || m_gauge.open}; auto const more{EditorSettings.track_tools_more()}; Buttons.push_back({icon::more, more ? STR_C("Less") : STR_C("More"), nullptr, STR_C("The checks of a line laid: profile, speed, joints, infrastructure, structure gauge"), more, [more]() { EditorSettings.track_tools_more(false == more); EditorSettings.save(); }, true}); if (false == more && false == advanced) break; add(track_tab::profile, icon::profile, true); add(track_tab::speed, icon::speed, false); add(track_tab::joints, icon::joints, false); add(track_tab::infra, icon::infra, false); Buttons.push_back({icon::gauge, STR_C("Structure gauge"), nullptr, STR_C("Checks which models enter the structure gauge of the tracks and the clearance over the roads (skrajnia budowli)"), m_gauge.open, [this]() { m_gauge.open = !m_gauge.open; }}); break; } case work_area::roads: { auto &tool{m_roadtool}; Buttons.push_back({icon::select, STR_C("Select"), nullptr, STR_C("LMB: a road piece, a junction, a point where pieces meet, a level crossing or a traffic point"), tool.tool == 0, [this]() { road_choose_tool(0); }}); Buttons.push_back({icon::road, STR_C("Build"), nullptr, STR_C("LMB: start a road, then each next point; on a loose end, the side of a road or a junction it's joined to them"), tool.tool == 1, [this]() { road_choose_tool(1); }}); Buttons.push_back({icon::place, STR_C("Place"), nullptr, STR_C("Level crossings, and the points where vehicles appear on the roads or are taken off them"), tool.tool == 2, [this]() { road_choose_tool(2); }}); if (tool.tool == 2) { // what the place tool puts char const *const kinds[] = {STR_C("Level crossing"), STR_C("Spawn point"), STR_C("Removal point"), STR_C("Pedestrian crossing")}; for (int index = 0; index < static_cast(std::size(kinds)); ++index) Buttons.push_back({icon::count, kinds[index], nullptr, nullptr, tool.placekind == index, [&tool, index]() { tool.placekind = index; }, index == 0}); } Buttons.push_back({icon::lanes, STR_C("Show lanes"), nullptr, STR_C("The lanes of the roads drawn as the paths the vehicles take"), tool.lanes, [&tool]() { tool.lanes = !tool.lanes; }, true}); break; } case work_area::terrain: { // what the left button does on the terrain auto const choose = [this](terrain_tool const Tool) { return [this, Tool]() { m_terrain_tool = Tool; }; }; auto const chosen = [this](terrain_tool const Tool) { return m_terrain_tool == Tool; }; Buttons.push_back({icon::select, STR_C("Select"), nullptr, STR_C("LMB picks the models, as in the surroundings"), chosen(terrain_tool::none), choose(terrain_tool::none)}); Buttons.push_back({icon::sculpt, STR_C("Sculpt"), nullptr, STR_C("LMB raises the terrain under the brush, Shift+LMB lowers it"), chosen(terrain_tool::sculpt), choose(terrain_tool::sculpt)}); Buttons.push_back({icon::smooth, STR_C("Smooth"), nullptr, STR_C("LMB evens the terrain out under the brush"), chosen(terrain_tool::smooth), choose(terrain_tool::smooth)}); Buttons.push_back({icon::profile, STR_C("Level"), nullptr, STR_C("LMB leads the terrain under the brush to the target height, Ctrl+LMB takes the target height from what's under the cursor"), chosen(terrain_tool::level), choose(terrain_tool::level)}); Buttons.push_back({icon::brush, STR_C("Paint"), nullptr, STR_C("LMB paints the material chosen in the palette, Shift+LMB the first material of the palette"), chosen(terrain_tool::paint), choose(terrain_tool::paint)}); Buttons.push_back({icon::chunks, STR_C("Chunks"), nullptr, STR_C("LMB adds a chunk next to the clicked one, Shift+LMB removes it"), chosen(terrain_tool::chunks), choose(terrain_tool::chunks)}); Buttons.push_back({icon::count, STR_C("Point spacing"), nullptr, STR_C("LMB selects the clicked chunk, Ctrl+LMB adds one or takes it away; the tool options convert the point spacing of the selected"), chosen(terrain_tool::spacing), choose(terrain_tool::spacing)}); Buttons.push_back({icon::area_fill, STR_C("Water"), nullptr, STR_C("LMB adds a point of the outline of a body of water, Shift+LMB takes the last one back"), chosen(terrain_tool::water), choose(terrain_tool::water)}); Buttons.push_back({icon::count, STR_C("Fill"), nullptr, STR_C("LMB adds a point of the outline of an area to fill with a material, Shift+LMB takes the last one back"), chosen(terrain_tool::fill), choose(terrain_tool::fill)}); Buttons.push_back({icon::joints, STR_C("Restore"), nullptr, STR_C("LMB takes back the touch-ups made over the tracks: the terrain returns to the shape the tracks give it"), chosen(terrain_tool::restore), choose(terrain_tool::restore)}); auto const orthophoto{m_orthophoto.enabled()}; Buttons.push_back({icon::orthophoto, STR_C("Orthophoto"), nullptr, STR_C("Aerial imagery of geoportal.gov.pl under the scenery, laid out by the origin of the scenery"), orthophoto, [this, orthophoto]() { m_orthophoto.enabled(false == orthophoto); }, true}); Buttons.push_back({icon::count, STR_C("Orthophoto settings..."), nullptr, nullptr, m_orthophoto_window, [this]() { m_orthophoto_window = !m_orthophoto_window; }}); break; } } } void editor_mode::render_viewport_tools() { // the tools of the field of work which have icons, as in the toolbar std::vector buttons; buttons.reserve(24); work_area_tools(buttons); std::erase_if(buttons, [](tool_button const &Button) { return Button.picture == icon::count; }); if (buttons.empty()) return; // down the left edge, in more columns if the view is too low for them; the bottom is left to the hints of the tools auto const side{button_side()}; auto const gap{2.0f * scale()}; auto const groupgap{8.0f * scale()}; auto const margin{8.0f * scale()}; ImVec2 const origin{editor_ui::view_min().x + margin, editor_ui::view_min().y + margin}; auto const bottom{std::max(origin.y + 4.0f * side, editor_ui::view_max().y - 130.0f * scale())}; std::vector places; places.reserve(buttons.size()); ImVec2 place{origin}; for (auto const &button : buttons) { if (button.group && place.y > origin.y) place.y += groupgap - gap; if (place.y > origin.y && place.y + side > bottom) place = ImVec2(place.x + side + gap, origin.y); places.push_back(place); place.y += side + gap; } ImVec2 extent{0.0f, 0.0f}; for (auto const &at : places) extent = ImVec2(std::max(extent.x, at.x + side - origin.x), std::max(extent.y, at.y + side - origin.y)); auto const pad{3.0f * scale()}; if (begin_overlay("##viewporttools", ImVec2(origin.x - pad, origin.y - pad), ImVec2(extent.x + 2.0f * pad, extent.y + 2.0f * pad))) { ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(origin.x - pad, origin.y - pad), ImVec2(origin.x + extent.x + pad, origin.y + extent.y + pad), IM_COL32(16, 18, 21, 140), 6.0f * scale()); for (std::size_t index = 0; index < buttons.size(); ++index) icon_button(buttons[index], places[index], static_cast(index)); } ImGui::End(); } void editor_mode::render_toolbar_caption(char const *Text) { // as wide in both rows, so the buttons of the rows start one under the other. the rows are drawn with no spacing of items auto const margin{std::round(ImGui::GetFontSize() * 0.75f)}; auto const width{std::max(ImGui::CalcTextSize(STR_C("Work modes")).x, ImGui::CalcTextSize(STR_C("Tools")).x) + 2.0f * margin}; auto const position{ImGui::GetWindowPos()}; auto const height{ImGui::GetWindowHeight()}; auto *list{ImGui::GetWindowDrawList()}; auto const text{ImGui::CalcTextSize(Text)}; list->AddText(ImVec2(position.x + margin, std::round(position.y + (height - text.y) * 0.5f)), ImGui::GetColorU32(ImGuiCol_TextDisabled), Text); list->AddLine(ImVec2(position.x + width, position.y + height * 0.2f), ImVec2(position.x + width, position.y + height * 0.8f), ImGui::GetColorU32(ImGuiCol_Separator)); ImGui::SetCursorScreenPos(position); ImGui::Dummy(ImVec2(width + margin * 0.5f, height)); ImGui::SameLine(0.0f, 0.0f); } void editor_mode::render_toolbar_tools() { render_toolbar_caption(STR_C("Tools")); std::vector buttons; buttons.reserve(24); work_area_tools(buttons); if (buttons.empty()) return; auto const s{scale()}; auto const iconside{editor_icons::size()}; auto const height{ImGui::GetWindowHeight()}; auto const margin{3.0f * s}; // over and under the buttons auto const pad{7.0f * s}; // inside the button, at its ends auto const spacing{5.0f * s}; // between the icon and the name auto const gap{3.0f * s}; auto const groupgap{15.0f * s}; auto const buttonheight{height - 2.0f * margin}; auto const left{ImGui::GetCursorScreenPos().x}; auto const top{ImGui::GetWindowPos().y + margin}; auto const right{ImGui::GetWindowPos().x + ImGui::GetWindowWidth() - 4.0f * s}; // the names next to the icons if there's room for them; if there isn't, the name of the chosen tool only, or none enum class names { all, chosen, none }; auto const named = [](tool_button const &Button, names const Names) { return Button.picture == icon::count || Names == names::all || (Names == names::chosen && Button.chosen); }; auto const width = [&](tool_button const &Button, names const Names) { if (false == named(Button, Names)) return std::max(buttonheight, iconside + pad); auto result{2.0f * pad + ImGui::CalcTextSize(Button.label).x}; if (Button.picture != icon::count) result += iconside + spacing; return result; }; auto const total = [&](names const Names) { auto result{0.0f}; for (std::size_t index = 0; index < buttons.size(); ++index) result += width(buttons[index], Names) + (index == 0 ? 0.0f : buttons[index].group ? groupgap : gap); return result; }; auto shown{names::none}; for (auto const option : {names::all, names::chosen}) if (left + total(option) <= right) { shown = option; break; } auto *list{ImGui::GetWindowDrawList()}; auto const rounding{3.0f * s}; auto x{left}; for (std::size_t index = 0; index < buttons.size(); ++index) { auto const &button{buttons[index]}; if (index > 0) { x += button.group ? groupgap : gap; if (button.group) list->AddLine(ImVec2(std::round(x - groupgap * 0.5f), top + buttonheight * 0.15f), ImVec2(std::round(x - groupgap * 0.5f), top + buttonheight * 0.85f), ImGui::GetColorU32(ImGuiCol_Separator)); } auto const size{ImVec2(width(button, shown), buttonheight)}; ImVec2 const position{x, top}; ImGui::SetCursorScreenPos(position); ImGui::PushID(static_cast(index)); auto const clicked{ImGui::InvisibleButton("##tool", size)}; ImGui::PopID(); auto const hovered{ImGui::IsItemHovered()}; ImVec2 const end{position.x + size.x, position.y + size.y}; // the chosen one stands out as the chosen field of work does, the others as buttons auto const background{button.chosen ? ImGui::GetColorU32(ImGuiCol_Header) : hovered && button.enabled ? ImGui::GetColorU32(ImGuiCol_HeaderHovered) : ImGui::GetColorU32(ImGuiCol_FrameBg)}; list->AddRectFilled(position, end, background, rounding); if (button.chosen) list->AddRectFilled(ImVec2(position.x, end.y - 2.0f * s), end, ImGui::GetColorU32(ImGuiCol_CheckMark), rounding, ImDrawFlags_RoundCornersBottom); auto const color{button.enabled ? ImGui::GetColorU32(ImGuiCol_Text, button.chosen || hovered ? 1.0f : 0.85f) : ImGui::GetColorU32(ImGuiCol_TextDisabled)}; auto const center{position.y + size.y * 0.5f}; if (named(button, shown)) { auto textx{position.x + pad}; if (button.picture != icon::count) { editor_icons::draw(list, button.picture, ImVec2(textx + iconside * 0.5f, center), color); textx += iconside + spacing; } list->AddText(ImVec2(textx, std::round(center - ImGui::GetFontSize() * 0.5f)), color, button.label); } else icon_or_letters(list, button, ImVec2(position.x + size.x * 0.5f, center), color); if (hovered) button_tooltip(button); if (clicked && button.enabled && button.action) button.action(); x = end.x; } } void editor_mode::render_viewport_gizmo() { // the gizmo moves the models and the tracks; on the terrain the mouse is the brush's if (false == m_gizmo_enabled || current_work_area() == work_area::terrain) return; auto const tooltip{STR_C("What the handles of the gizmo on the selection do when dragged; Ctrl held snaps the values")}; std::array const buttons{{ {icon::translate, STR_C("Translate"), "Q", tooltip, m_gizmo_op == gizmo_operation::translate, [this]() { m_gizmo_op = gizmo_operation::translate; }}, {icon::rotate, STR_C("Rotate"), "W", tooltip, m_gizmo_op == gizmo_operation::rotate, [this]() { m_gizmo_op = gizmo_operation::rotate; }}, {icon::scale, STR_C("Scale"), "E", tooltip, m_gizmo_op == gizmo_operation::scale, [this]() { m_gizmo_op = gizmo_operation::scale; }}, // the gizmo always scales along the axes of the model {icon::local_space, STR_C("Local space"), "R", STR_C("The handles along the axes of the selected model instead of the axes of the world"), m_gizmo_local || m_gizmo_op == gizmo_operation::scale, [this]() { m_gizmo_local = !m_gizmo_local; }, true, m_gizmo_op != gizmo_operation::scale}, }}; // in a row at the top, in the middle auto const side{button_side()}; auto const gap{2.0f * scale()}; auto const groupgap{8.0f * scale()}; auto const margin{8.0f * scale()}; auto const pad{3.0f * scale()}; auto const width{4.0f * side + 2.0f * gap + groupgap}; ImVec2 const origin{std::round((editor_ui::view_min().x + editor_ui::view_max().x - width) * 0.5f), editor_ui::view_min().y + margin}; if (begin_overlay("##viewportgizmo", ImVec2(origin.x - pad, origin.y - pad), ImVec2(width + 2.0f * pad, side + 2.0f * pad))) { ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(origin.x - pad, origin.y - pad), ImVec2(origin.x + width + pad, origin.y + side + pad), IM_COL32(16, 18, 21, 140), 6.0f * scale()); auto x{origin.x}; for (std::size_t index = 0; index < buttons.size(); ++index) { if (buttons[index].group) x += groupgap - gap; icon_button(buttons[index], ImVec2(x, origin.y), static_cast(index)); x += side + gap; } } ImGui::End(); } void editor_mode::render_view_axes() { // the axes of the world as the camera sees them, as in blender: a click on one looks along it auto const s{scale()}; auto const radius{40.0f * s}; auto const margin{8.0f * s}; auto const side{button_side()}; auto const size{2.0f * radius}; ImVec2 const origin{editor_ui::view_max().x - margin - size, editor_ui::view_min().y + margin}; if (begin_overlay("##viewaxes", origin, ImVec2(size, size + 6.0f * s + side))) { auto *list{ImGui::GetWindowDrawList()}; ImVec2 const center{origin.x + radius, origin.y + radius}; ImGui::SetCursorScreenPos(origin); ImGui::InvisibleButton("##axes", ImVec2(size, size)); auto const hovered{ImGui::IsItemHovered()}; auto const clicked{ImGui::IsItemClicked(ImGuiMouseButton_Left)}; if (hovered) list->AddCircleFilled(center, radius, IM_COL32(255, 255, 255, 28), 48); // the view matrix is of the camera turned only, the axes go through it as they are glm::mat3 const view{GfxRenderer->Camera_View_Matrix()}; struct end { int axis; bool positive; glm::vec3 seen; }; std::array ends; for (int axis = 0; axis < 3; ++axis) { glm::vec3 direction{0.0f}; direction[axis] = 1.0f; auto const seen{view * direction}; ends[axis * 2] = {axis, true, seen}; ends[axis * 2 + 1] = {axis, false, -seen}; } // the farther ones first, the nearer ones over them std::sort(ends.begin(), ends.end(), [](end const &Left, end const &Right) { return Left.seen.z < Right.seen.z; }); ImU32 const colors[]{IM_COL32(230, 70, 80, 255), IM_COL32(130, 200, 40, 255), IM_COL32(60, 135, 235, 255)}; char const *const names[]{"X", "Y", "Z"}; auto const reach{radius - 10.0f * s}; auto const mouse{ImGui::GetIO().MousePos}; end const *pointed{nullptr}; auto const place = [&](end const &End) { return ImVec2(center.x + End.seen.x * reach, center.y - End.seen.y * reach); }; for (auto const &entry : ends) { auto const at{place(entry)}; auto const dot{(entry.positive ? 8.0f : 6.0f) * s}; if (hovered && (mouse.x - at.x) * (mouse.x - at.x) + (mouse.y - at.y) * (mouse.y - at.y) <= (dot + 2.0f * s) * (dot + 2.0f * s)) pointed = &entry; // the nearest one under the mouse, as it's drawn over the others } for (auto const &entry : ends) { auto const at{place(entry)}; auto const color{colors[entry.axis]}; if (entry.positive) { list->AddLine(center, at, color, 2.0f * s); list->AddCircleFilled(at, 8.0f * s, color, 24); auto const text{ImGui::CalcTextSize(names[entry.axis])}; list->AddText(ImVec2(at.x - text.x * 0.5f, at.y - text.y * 0.5f), IM_COL32(20, 20, 20, 255), names[entry.axis]); } else { list->AddCircleFilled(at, 6.0f * s, (color & ~IM_COL32_A_MASK) | IM_COL32(0, 0, 0, 90), 24); list->AddCircle(at, 6.0f * s, color, 24, 1.5f * s); } if (&entry == pointed) list->AddCircle(at, (entry.positive ? 9.5f : 7.5f) * s, IM_COL32(255, 255, 255, 255), 24, 1.5f * s); } if (pointed != nullptr) { ImGui::SetTooltip(STR_C("Look along %s%s; from over the scenery it's the top view"), pointed->positive ? "-" : "+", names[pointed->axis]); if (clicked) look_along_axis(pointed->axis, pointed->positive); } // the top view, under the axes tool_button const ortho{Global.EditorOrtho ? icon::ortho : icon::perspective, STR_C("Top view, orthographic"), "O", STR_C("The view straight down without perspective, as on a map; again for the camera as it was"), Global.EditorOrtho, [this]() { toggle_ortho(); }}; icon_button(ortho, ImVec2(std::round(center.x - side * 0.5f), origin.y + size + 6.0f * s), 0); } ImGui::End(); } void editor_mode::look_along_axis(int const Axis, bool const Positive) { m_focus_active = false; if (Axis == 1) { if (Positive) { // from over the scenery: the top view if (false == Global.EditorOrtho) toggle_ortho(); return; } if (Global.EditorOrtho) toggle_ortho(); Camera.Angle.x = M_PI_2; Camera.Angle.z = 0.0; return; } if (Global.EditorOrtho) toggle_ortho(); // from the side of the axis the camera's on, towards the other one; the yaw as the camera works it out of where it looks glm::dvec3 where{0.0}; where[Axis] = Positive ? -1.0 : 1.0; Camera.Angle.y = std::atan2(-where.x, -where.z); Camera.Angle.x = 0.0; Camera.Angle.z = 0.0; }