diff --git a/far/dialog.cpp b/far/dialog.cpp index 01bbe1d87bc..fff6d293583 100644 --- a/far/dialog.cpp +++ b/far/dialog.cpp @@ -2540,6 +2540,7 @@ bool Dialog::ProcessKey(const Manager::Key& Key) case KEY_MSWHEEL_RIGHT: case KEY_NUMENTER: case KEY_ENTER: + case KEY_NUMPAD5: auto& List = Items[m_FocusPos].ListPtr; int CurListPos=List->GetSelectPos(); List->ProcessKey(Key); diff --git a/far/editor.cpp b/far/editor.cpp index 36522ea40ea..ef1b47cd929 100644 --- a/far/editor.cpp +++ b/far/editor.cpp @@ -3772,10 +3772,10 @@ void Editor::DoSearchReplace(const SearchReplaceDisposition Disposition) case KEY_CTRLENTER: case KEY_RCTRLENTER: - // TODO: Need to handle mouse events with ProcessMouse(). This implementation is accpetable, + // TODO: Need to handle mouse events with ProcessMouse(). This implementation is acceptable, // but a click on the border jumps to the currently selected item without changing the item. // Normally, mouse click on the border frame does nothing. Mouse click on the border - // ouside of the frame starts dragging the dialog. + // outside of the frame starts dragging the dialog. case KEY_CTRL|KEY_MSLCLICK: case KEY_RCTRL|KEY_MSLCLICK: { diff --git a/far/vmenu.cpp b/far/vmenu.cpp index 4234dcf1e99..3f7ef646860 100644 --- a/far/vmenu.cpp +++ b/far/vmenu.cpp @@ -78,13 +78,270 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //---------------------------------------------------------------------------- -static MenuItemEx FarList2MenuItem(const FarListItem& FItem) +// Must be in the TU scope because it's a friend of VMenu +struct item_layout { - MenuItemEx Result; - Result.Flags = FItem.Flags; - Result.Name = NullToEmpty(FItem.Text); - Result.SimpleUserData = FItem.UserData; - return Result; + std::optional LeftBox; + std::optional CheckMark; + std::optional LeftHScroll; + std::optional> Text; // Begin, Width + std::optional RightHScroll; + std::optional SubMenu; + std::optional Scrollbar; + std::optional RightBox; + + item_layout(const VMenu& Menu, int BoxType) + { + auto Left{ Menu.m_Where.left }; + if (need_box(BoxType)) LeftBox = Left++; + if (need_check_mark()) CheckMark = Left++; + if (need_left_hscroll()) LeftHScroll = Left++; + + auto Right{ Menu.m_Where.right }; + if (need_box(BoxType)) RightBox = Right; + if (need_scrollbar(Menu)) Scrollbar = Right; + if (RightBox || Scrollbar) Right--; + if (need_submenu(Menu)) SubMenu = Right--; + if (need_right_hscroll()) RightHScroll = Right--; + + if (Left <= Right) + Text = { Left, Right + 1 - Left }; + } + + [[nodiscard]] static bool need_box(int BoxType) noexcept { return BoxType != NO_BOX; } + [[nodiscard]] static bool need_check_mark() noexcept { return true; } + [[nodiscard]] static bool need_left_hscroll() noexcept { return true; } + [[nodiscard]] static bool need_right_hscroll() noexcept { return true; } + [[nodiscard]] static bool need_submenu(const VMenu& Menu) noexcept { return Menu.ItemSubMenusCount > 0; }; + [[nodiscard]] static bool need_scrollbar(const VMenu& Menu) + { + return (Menu.CheckFlags(VMENU_LISTBOX | VMENU_ALWAYSSCROLLBAR) || Global->Opt->ShowMenuScrollbar) + && ScrollBarRequired(Menu.m_Where.height(), Menu.GetShowItemCount()); + } + + [[nodiscard]] static size_t GetServiceAreaSize(const VMenu& Menu, const int BoxType) + { + return need_box(BoxType) + + need_check_mark() + + need_left_hscroll() + + need_right_hscroll() + + need_submenu(Menu) + + (need_box(BoxType) || need_scrollbar(Menu)); + } +}; + +namespace +{ + MenuItemEx FarList2MenuItem(const FarListItem& FItem) + { + MenuItemEx Result; + Result.Flags = FItem.Flags; + Result.Name = NullToEmpty(FItem.Text); + Result.SimpleUserData = FItem.UserData; + return Result; + } + + int find_nearest(std::ranges::contiguous_range auto const& Range, const int Pos, const auto Pred, const bool GoBackward, const bool DoWrap) + { + using namespace std::views; + + assert(0 <= Pos && Pos < static_cast(Range.size())); + + const auto FindPos = + [&](const auto First, const auto Second) + { + const auto FindPosPart = + [&](const auto Part) + { + if (auto Filtered = Range | Part | filter(Pred)) + return static_cast(&Filtered.front() - Range.data()); + + return -1; + }; + + if (const auto Found{ FindPosPart(First) }; Found != -1) return Found; + if (const auto Found{ FindPosPart(Second) }; Found != -1) return Found; + return -1; + }; + + return GoBackward + ? (DoWrap + ? FindPos(take(Pos + 1) | reverse, drop(Pos + 1) | reverse) + : FindPos(take(Pos + 1) | reverse, drop(Pos + 1))) + : (DoWrap + ? FindPos(drop(Pos), take(Pos)) + : FindPos(drop(Pos), take(Pos) | reverse)); + } + + std::pair Intersect(std::pair A, std::pair B) + { + assert(A.first < A.second); + assert(B.first < B.second); + + if (B.first < A.first) + std::ranges::swap(A, B); + + if (A.second <= B.first) + return {}; + + return { B.first, std::min(A.second, B.second) }; + } + + void MarkupSliceBoundaries(std::pair Segment, std::ranges::input_range auto const& Slices, std::vector& Markup) + { + assert(Segment.first < Segment.second); + + for (const auto& Slice : Slices) + { + if (Slice.first >= Slice.second) + continue; + + const auto Intersection{ Intersect(Segment, Slice) }; + + if (Intersection.first == Intersection.second) + continue; + + Markup.emplace_back(Intersection.first); + Markup.emplace_back(Intersection.second); + Segment.first = Intersection.second; + + if (Segment.first == Segment.second) + return; + } + + Markup.emplace_back(Segment.second); + } + + bool item_flags_allow_focus(unsigned long long const Flags) + { + return !(Flags & (LIF_DISABLE | LIF_HIDDEN | LIF_FILTERED | LIF_SEPARATOR)); + } + + bool item_can_have_focus(MenuItemEx const& Item) + { + return item_flags_allow_focus(Item.Flags); + } + + bool item_can_be_entered(MenuItemEx const& Item) + { + return item_can_have_focus(Item) && !(Item.Flags & LIF_GRAYED); + } + + bool item_is_visible(MenuItemEx const& Item) + { + return !(Item.Flags & (LIF_HIDDEN | LIF_FILTERED)); + } + + std::pair GetItemHPosLimits(const int ItemLength, const int MaxItemWidth, const bool Justify, const bool StickToZero) + { + if (!Justify) return{ 1 - ItemLength, MaxItemWidth - 1 }; + + return{ std::min(0, MaxItemWidth - ItemLength), StickToZero ? 0 : std::max(0, MaxItemWidth - ItemLength) }; + } + + // Indices in the color array + enum class color_indices + { + Body = 0, // background + Box = 1, // border + Title = 2, // title - top and bottom + Text = 3, // item text + Highlight = 4, // hot key + Separator = 5, // separator + Selected = 6, // selected + HSelect = 7, // selected - HotKey + ScrollBar = 8, // scrollBar + Disabled = 9, // disabled + Arrows =10, // '«' & '»' normal + ArrowsSelect =11, // '«' & '»' selected + ArrowsDisabled =12, // '«' & '»' disabled + Grayed =13, // grayed + SelGrayed =14, // selected grayed + + COUNT // always the last - array dimension + }; + + static_assert(std::tuple_size_v == std::to_underlying(color_indices::COUNT)); + + [[nodiscard]] const FarColor& GetColor(const vmenu_colors_t& VMenuColors, color_indices ColorIndex) noexcept + { + return VMenuColors[std::to_underlying(ColorIndex)]; + } + + void SetColor(const vmenu_colors_t& VMenuColors, color_indices ColorIndex) + { + ::SetColor(GetColor(VMenuColors, ColorIndex)); + } + + struct item_color_indicies + { + color_indices Normal, Highlighted, HScroller; + + item_color_indicies(const MenuItemEx& CurItem) + { + const auto Selected{ !!(CurItem.Flags & LIF_SELECTED) }; + const auto Grayed{ !!(CurItem.Flags & LIF_GRAYED) }; + const auto Disabled{ !!(CurItem.Flags & LIF_DISABLE) }; + + if (Disabled) + { + Normal = color_indices::Disabled; + Highlighted = color_indices::Disabled; + HScroller = color_indices::ArrowsDisabled; + return; + } + + if (Selected) + { + Normal = Grayed ? color_indices::SelGrayed : color_indices::Selected; + Highlighted = Grayed ? color_indices::SelGrayed : color_indices::HSelect; + HScroller = color_indices::ArrowsSelect; + return; + } + + Normal = Grayed ? color_indices::Grayed : color_indices::Text; + Highlighted = Grayed ? color_indices::Grayed : color_indices::Highlight; + HScroller = color_indices::Arrows; + } + }; + + std::tuple GetItemCheckMark(const MenuItemEx& CurItem, item_color_indicies ColorIndices) noexcept + { + return + { + ColorIndices.Normal, + !(CurItem.Flags & LIF_CHECKED) + ? L' ' + : !(CurItem.Flags & 0x0000FFFF) ? L'√' : static_cast(CurItem.Flags & 0x0000FFFF) + }; + } + + std::tuple GetItemSubMenu(const MenuItemEx& CurItem, item_color_indicies ColorIndices) noexcept + { + return + { + ColorIndices.Normal, + (CurItem.Flags & MIF_SUBMENU) ? L'►' : L' ' + }; + } + + std::tuple GetItemLeftHScroll(const bool NeedLeftHScroll, item_color_indicies ColorIndices) noexcept + { + return + { + NeedLeftHScroll ? ColorIndices.HScroller : ColorIndices.Normal, + NeedLeftHScroll ? L'«' : L' ' + }; + } + + std::tuple GetItemRightHScroll(const bool NeedRightHScroll, item_color_indicies ColorIndices) noexcept + { + return + { + NeedRightHScroll ? ColorIndices.HScroller : ColorIndices.Normal, + NeedRightHScroll ? L'»' : L' ' + }; + } } VMenu::VMenu(private_tag, string Title, int MaxHeight, dialog_ptr ParentDialog): @@ -139,100 +396,6 @@ void VMenu::ResetCursor() GetCursorType(PrevCursorVisible,PrevCursorSize); } -int find_nearest(std::ranges::contiguous_range auto const& Range, const int Pos, const auto Pred, const bool GoBackward, const bool DoWrap) -{ - using namespace std::views; - - assert(0 <= Pos && Pos < static_cast(Range.size())); - - const auto FindPos = - [&](const auto First, const auto Second) - { - const auto FindPosPart = - [&](const auto Part) - { - if (auto Filtered = Range | Part | filter(Pred)) - return static_cast(&Filtered.front() - Range.data()); - - return -1; - }; - - if (const auto Found{ FindPosPart(First) }; Found != -1) return Found; - if (const auto Found{ FindPosPart(Second) }; Found != -1) return Found; - return -1; - }; - - return GoBackward - ? (DoWrap - ? FindPos(take(Pos + 1) | reverse, drop(Pos + 1) | reverse) - : FindPos(take(Pos + 1) | reverse, drop(Pos + 1))) - : (DoWrap - ? FindPos(drop(Pos), take(Pos)) - : FindPos(drop(Pos), take(Pos) | reverse)); -} - -std::pair Intersect(std::pair A, std::pair B) -{ - assert(A.first < A.second); - assert(B.first < B.second); - - if (B.first < A.first) - std::ranges::swap(A, B); - - if (A.second <= B.first) - return {}; - - return { B.first, std::min(A.second, B.second) }; -} - -void MarkupSliceBoundaries(std::pair Segment, std::ranges::input_range auto const& Slices, std::vector& Markup) -{ - assert(Segment.first < Segment.second); - - for (const auto& Slice : Slices) - { - if (Slice.first >= Slice.second) - continue; - - const auto Intersection{ Intersect(Segment, Slice) }; - - if (Intersection.first == Intersection.second) - continue; - - Markup.emplace_back(Intersection.first); - Markup.emplace_back(Intersection.second); - Segment.first = Intersection.second; - - if (Segment.first == Segment.second) - return; - } - - Markup.emplace_back(Segment.second); -} - -//может иметь фокус -static bool item_flags_allow_focus(unsigned long long const Flags) -{ - return !(Flags & (LIF_DISABLE | LIF_HIDDEN | LIF_FILTERED | LIF_SEPARATOR)); -} - -static bool item_can_have_focus(MenuItemEx const& Item) -{ - return item_flags_allow_focus(Item.Flags); -} - -//может быть выбран -static bool item_can_be_entered(MenuItemEx const& Item) -{ - return item_can_have_focus(Item) && !(Item.Flags & LIF_GRAYED); -} - -//видимый -static bool item_is_visible(MenuItemEx const& Item) -{ - return !(Item.Flags & (LIF_HIDDEN | LIF_FILTERED)); -} - bool VMenu::UpdateRequired() const { return CheckFlags(VMENU_UPDATEREQUIRED)!=0; @@ -473,7 +636,7 @@ int VMenu::AddItem(MenuItemEx&& NewItem,int PosAdd) NewMenuItem.AutoHotkey = {}; NewMenuItem.AutoHotkeyPos = 0; - NewMenuItem.ShowPos = 0; + NewMenuItem.HPos = 0; if (PosAdd <= SelectPos) SelectPos++; @@ -1362,6 +1525,13 @@ bool VMenu::ProcessKey(const Manager::Key& Key) break; } + case KEY_CTRLNUMPAD5: + { + if (AlignAnnotations(CalculateMaxLineWidth())) + DrawMenu(); + + break; + } case KEY_MSWHEEL_UP: { SetSelectPos(SelectPos - 1, -1, true); @@ -1808,16 +1978,12 @@ int VMenu::VisualPosToReal(int VPos) const return ItemIterator != Items.cend()? ItemIterator - Items.cbegin() : -1; } -size_t VMenu::GetItemMaxShowPos(int Item, const size_t MaxLineWidth) const -{ - const auto Len{ VMFlags.Check(VMENU_SHOWAMPERSAND)? visual_string_length(Items[Item].Name) : HiStrlen(Items[Item].Name) }; - if (Len <= MaxLineWidth) - return 0; - return Len - MaxLineWidth; -} +// const auto ItemLength{ static_cast(VMFlags.Check(VMENU_SHOWAMPERSAND) ? visual_string_length(Items[Item].Name) : HiStrlen(Items[Item].Name)) }; bool VMenu::SetItemShowPos(int Item, int NewShowPos, const size_t MaxLineWidth) { + return false; +#if 0 const auto MaxShowPos{ GetItemMaxShowPos(Item, MaxLineWidth) }; auto OldShowPos{ Items[Item].ShowPos }; @@ -1835,10 +2001,13 @@ bool VMenu::SetItemShowPos(int Item, int NewShowPos, const size_t MaxLineWidth) VMFlags.Set(VMENU_UPDATEREQUIRED); return true; +#endif } bool VMenu::ShiftItemShowPos(int Item, int Shift, const size_t MaxLineWidth) { + return false; +#if 0 const auto MaxShowPos{ GetItemMaxShowPos(Item, MaxLineWidth) }; auto ItemShowPos{ std::min(Items[Item].ShowPos, MaxShowPos) }; // Just in case @@ -1857,6 +2026,7 @@ bool VMenu::ShiftItemShowPos(int Item, int Shift, const size_t MaxLineWidth) Items[Item].ShowPos = ItemShowPos; VMFlags.Set(VMENU_UPDATEREQUIRED); return true; +#endif } bool VMenu::SetAllItemsShowPos(int NewShowPos) @@ -1883,56 +2053,35 @@ bool VMenu::ShiftAllItemsShowPos(int Shift) return NeedRedraw; } -struct item_layout +bool VMenu::AlignAnnotations(const size_t MaxLineWidth) { - std::optional LeftBox; - std::optional CheckMark; - std::optional LeftHScroll; - std::optional> Text; // Begin, Width - std::optional RightHScroll; - std::optional SubMenu; - std::optional Scrollbar; - std::optional RightBox; + return false; +#if 0 + if (MaxLineWidth == 0 || MaxLineWidth + 2 > std::numeric_limits::max()) return false; - item_layout(const VMenu& Menu, int BoxType) + const auto AlignPos{ static_cast((MaxLineWidth + 2) / 4) }; + + bool NeedRedraw = false; + + for (auto& Item : Items) { - auto Left{ Menu.m_Where.left }; - if (need_box(BoxType)) LeftBox = Left++; - if (need_check_mark()) CheckMark = Left++; - if (need_left_hscroll()) LeftHScroll = Left++; + if (Item.Annotations.empty()) continue; - auto Right{ Menu.m_Where.right }; - if (need_box(BoxType)) RightBox = Right; - if (need_scrollbar(Menu)) Scrollbar = Right; - if (RightBox || Scrollbar) Right--; - if (need_submenu(Menu)) SubMenu = Right--; - if (need_right_hscroll()) RightHScroll = Right--; + const auto ItemLen{ VMFlags.Check(VMENU_SHOWAMPERSAND)? visual_string_length(Item.Name) : HiStrlen(Item.Name) }; + const auto Annotation{ Intersect(Item.Annotations.front(), { 0, static_cast(ItemLen) }) }; + if (Annotation.first == Annotation.second) continue; - if (Left <= Right) - Text = { Left, Right + 1 - Left }; - } + const auto NewHPos{ AlignPos - Annotation.first }; + if (NewHPos == Item.HPos) continue; - [[nodiscard]] static bool need_box(int BoxType) noexcept { return BoxType != NO_BOX; } - [[nodiscard]] static bool need_check_mark() noexcept { return true; } - [[nodiscard]] static bool need_left_hscroll() noexcept { return true; } - [[nodiscard]] static bool need_right_hscroll() noexcept { return true; } - [[nodiscard]] static bool need_submenu(const VMenu& Menu) noexcept { return Menu.ItemSubMenusCount > 0; }; - [[nodiscard]] static bool need_scrollbar(const VMenu& Menu) - { - return (Menu.CheckFlags(VMENU_LISTBOX | VMENU_ALWAYSSCROLLBAR) || Global->Opt->ShowMenuScrollbar) - && ScrollBarRequired(Menu.m_Where.height(), Menu.GetShowItemCount()); + Item.HPos = NewHPos; + VMFlags.Set(VMENU_UPDATEREQUIRED); + NeedRedraw = true; } - [[nodiscard]] static size_t GetServiceAreaSize(const VMenu& Menu, const int BoxType) - { - return need_box(BoxType) - + need_check_mark() - + need_left_hscroll() - + need_right_hscroll() - + need_submenu(Menu) - + (need_box(BoxType) || need_scrollbar(Menu)); - } -}; + return NeedRedraw; +#endif +} void VMenu::Show() { @@ -2033,113 +2182,6 @@ void VMenu::Hide() SetMenuFlags(VMENU_UPDATEREQUIRED); } -namespace -{ - // Indices in the color array - enum class color_indices - { - Body = 0, // background - Box = 1, // border - Title = 2, // title - top and bottom - Text = 3, // item text - Highlight = 4, // hot key - Separator = 5, // separator - Selected = 6, // selected - HSelect = 7, // selected - HotKey - ScrollBar = 8, // scrollBar - Disabled = 9, // disabled - Arrows =10, // '«' & '»' normal - ArrowsSelect =11, // '«' & '»' selected - ArrowsDisabled =12, // '«' & '»' disabled - Grayed =13, // grayed - SelGrayed =14, // selected grayed - - COUNT // always the last - array dimension - }; - - static_assert(std::tuple_size_v == std::to_underlying(color_indices::COUNT)); - - [[nodiscard]] const FarColor& GetColor(const vmenu_colors_t& VMenuColors, color_indices ColorIndex) noexcept - { - return VMenuColors[std::to_underlying(ColorIndex)]; - } - - void SetColor(const vmenu_colors_t& VMenuColors, color_indices ColorIndex) - { - ::SetColor(GetColor(VMenuColors, ColorIndex)); - } - - struct item_color_indicies - { - color_indices Normal, Highlighted, HScroller; - - item_color_indicies(const MenuItemEx& CurItem) - { - const auto Selected{ !!(CurItem.Flags & LIF_SELECTED) }; - const auto Grayed{ !!(CurItem.Flags & LIF_GRAYED) }; - const auto Disabled{ !!(CurItem.Flags & LIF_DISABLE) }; - - if (Disabled) - { - Normal = color_indices::Disabled; - Highlighted = color_indices::Disabled; - HScroller = color_indices::ArrowsDisabled; - return; - } - - if (Selected) - { - Normal = Grayed ? color_indices::SelGrayed : color_indices::Selected; - Highlighted = Grayed ? color_indices::SelGrayed : color_indices::HSelect; - HScroller = color_indices::ArrowsSelect; - return; - } - - Normal = Grayed ? color_indices::Grayed : color_indices::Text; - Highlighted = Grayed ? color_indices::Grayed : color_indices::Highlight; - HScroller = color_indices::Arrows; - } - }; - - std::tuple GetItemCheckMark(const MenuItemEx& CurItem, item_color_indicies ColorIndices) noexcept - { - return - { - ColorIndices.Normal, - !(CurItem.Flags & LIF_CHECKED) - ? L' ' - : !(CurItem.Flags & 0x0000FFFF) ? L'√' : static_cast(CurItem.Flags & 0x0000FFFF) - }; - } - - std::tuple GetItemSubMenu(const MenuItemEx& CurItem, item_color_indicies ColorIndices) noexcept - { - return - { - ColorIndices.Normal, - (CurItem.Flags & MIF_SUBMENU) ? L'►' : L' ' - }; - } - - std::tuple GetItemLeftHScroll(const bool NeedLeftHScroll, item_color_indicies ColorIndices) noexcept - { - return - { - NeedLeftHScroll ? ColorIndices.HScroller : ColorIndices.Normal, - NeedLeftHScroll ? L'«' : L' ' - }; - } - - std::tuple GetItemRightHScroll(const bool NeedRightHScroll, item_color_indicies ColorIndices) noexcept - { - return - { - NeedRightHScroll ? ColorIndices.HScroller : ColorIndices.Normal, - NeedRightHScroll ? L'»' : L' ' - }; - } -} - void VMenu::DisplayObject() { const auto Parent = GetDialog(); @@ -2431,6 +2473,7 @@ void VMenu::ApplySeparatorName(const MenuItemEx& CurItem, string& separator) con void VMenu::DrawRegularItem(const MenuItemEx& CurItem, const item_layout& Layout, const int Y, std::vector& HighlightMarkup, const string_view BlankLine) const { +#if 0 if (!Layout.Text) return; size_t HotkeyPos = string::npos; @@ -2498,6 +2541,7 @@ void VMenu::DrawRegularItem(const MenuItemEx& CurItem, const item_layout& Layout if (Layout.RightHScroll) DrawDecorator(Layout.RightHScroll.value(), GetItemRightHScroll(EndPos < ItemTextSize, ColorIndices)); +#endif } int VMenu::CheckHighlights(wchar_t CheckSymbol, int StartPos) const @@ -2578,7 +2622,9 @@ void VMenu::AssignHighlights(bool Reverse) if (!ShowAmpersand && HotkeyVisualPos != string::npos) continue; +#if 0 MenuItemForDisplay.erase(0, Items[I].ShowPos); +#endif // TODO: проверка на LIF_HIDDEN for (const auto& Ch: MenuItemForDisplay) @@ -3226,4 +3272,28 @@ TEST_CASE("markup.slice.boundaries") } } +TEST_CASE("get.item.hpos.limits") +{ + static constexpr struct test_data + { + int ItemLength; + int MaxItemWidth; + std::pair ExpectedLimitsHanging; + std::pair ExpectedLimitsJustify; + std::pair ExpectedLimitsJustifyStickToZero; + } TestDataPoints[] = + { + { 3, 5, { -2, 4 }, { 0, 2 }, { 0, 0 } }, + { 5, 3, { -4, 2 }, { -2, 0 }, { -2, 0 } }, + { 3, 3, { -2, 2 }, { 0, 0 }, { 0, 0 } }, + }; + + for (const auto& TestDataPoint : TestDataPoints) + { + REQUIRE(TestDataPoint.ExpectedLimitsHanging == GetItemHPosLimits(TestDataPoint.ItemLength, TestDataPoint.MaxItemWidth, false, false)); + REQUIRE(TestDataPoint.ExpectedLimitsHanging == GetItemHPosLimits(TestDataPoint.ItemLength, TestDataPoint.MaxItemWidth, false, true)); + REQUIRE(TestDataPoint.ExpectedLimitsJustify == GetItemHPosLimits(TestDataPoint.ItemLength, TestDataPoint.MaxItemWidth, true, false)); + REQUIRE(TestDataPoint.ExpectedLimitsJustifyStickToZero == GetItemHPosLimits(TestDataPoint.ItemLength, TestDataPoint.MaxItemWidth, true, true)); + } +} #endif diff --git a/far/vmenu.hpp b/far/vmenu.hpp index f458c8958ec..f9bb706b81b 100644 --- a/far/vmenu.hpp +++ b/far/vmenu.hpp @@ -142,12 +142,13 @@ struct MenuItemEx: menu_item std::any ComplexUserData; intptr_t SimpleUserData{}; - size_t ShowPos{}; + int HPos{}; // Positive: Indent; Negative: Hanging wchar_t AutoHotkey{}; size_t AutoHotkeyPos{}; - short Len[2]{}; // размеры 2-х частей - short Idx2{}; // начало 2-й части std::list> Annotations; + + int Indent() const noexcept { return std::max(0, HPos); } + int Hanging() const noexcept { return std::max(0, -HPos); } }; struct item_layout; @@ -297,13 +298,15 @@ class VMenu final: public Modal bool CheckKeyHiOrAcc(DWORD Key, int Type, bool Translate, bool ChangePos, int& NewPos); int CheckHighlights(wchar_t CheckSymbol,int StartPos=0) const; wchar_t GetHighlights(const MenuItemEx *Item) const; - size_t GetItemMaxShowPos(int Item, size_t MaxLineWidth) const; + // Negative NewShowPos is relative to the right side; -1 aligns the item to the right - bool SetItemShowPos(int Item, int NewShowPos, size_t MaxLineWidth); + [[nodiscard]] bool SetItemShowPos(int Item, int NewShowPos, size_t MaxLineWidth); // Shifts item's ShowPos; if Shift is positive, the item visually moves left - bool ShiftItemShowPos(int Item,int Shift, size_t MaxLineWidth); - bool SetAllItemsShowPos(int NewShowPos); - bool ShiftAllItemsShowPos(int Shift); + [[nodiscard]] bool ShiftItemShowPos(int Item,int Shift, size_t MaxLineWidth); + [[nodiscard]] bool SetAllItemsShowPos(int NewShowPos); + [[nodiscard]] bool ShiftAllItemsShowPos(int Shift); + [[nodiscard]] bool AlignAnnotations(size_t MaxLineWidth); + void UpdateMaxLengthFromTitles(); void UpdateMaxLength(size_t Length); bool ShouldSendKeyToFilter(unsigned Key) const;