From 8dde8e2856045800db3e0c47f239509c80faa102 Mon Sep 17 00:00:00 2001 From: "Michael Z. Kadaner" Date: Sun, 21 Jan 2024 20:21:42 -0800 Subject: [PATCH] WIP --- far/vmenu.cpp | 316 ++++++++++++++++++++++++++++++++------------------ far/vmenu.hpp | 13 ++- 2 files changed, 211 insertions(+), 118 deletions(-) diff --git a/far/vmenu.cpp b/far/vmenu.cpp index 1d1823d4e2..36be448925 100644 --- a/far/vmenu.cpp +++ b/far/vmenu.cpp @@ -136,12 +136,12 @@ struct menu_layout return DOUBLE_BOX; } - [[nodiscard]] static size_t get_service_area_size(const VMenu& Menu) + [[nodiscard]] static int get_service_area_size(const VMenu& Menu) { return get_service_area_size(Menu, need_box(Menu)); } - [[nodiscard]] static size_t get_service_area_size(const VMenu& Menu, const short BoxType) + [[nodiscard]] static int get_service_area_size(const VMenu& Menu, const short BoxType) { return get_service_area_size(Menu, need_box(BoxType)); } @@ -155,7 +155,7 @@ struct menu_layout return { Menu.m_Where.left + 1, Menu.m_Where.top + 1, Menu.m_Where.right - 1, Menu.m_Where.bottom - 1 }; } - [[nodiscard]] static size_t get_service_area_size(const VMenu& Menu, const bool NeedBox) + [[nodiscard]] static int get_service_area_size(const VMenu& Menu, const bool NeedBox) { return NeedBox + need_check_mark() @@ -282,20 +282,29 @@ namespace return !(Item.Flags & (LIF_HIDDEN | LIF_FILTERED)); } + int get_item_visual_length(const bool ShowAmpersand, const string_view ItemName) + { + return static_cast(ShowAmpersand ? visual_string_length(ItemName) : HiStrlen(ItemName)); + } + enum class item_hscroll_policy { + unbound, cling_to_edge, bound, bound_stick_to_left }; - std::pair item_hpos_limits(const item_hscroll_policy Policy, const int ItemLength, const int TextAreaWidth) noexcept + std::pair item_hpos_limits(const int ItemLength, const int TextAreaWidth, const item_hscroll_policy Policy) noexcept { assert(ItemLength > 0); assert(TextAreaWidth > 0); switch (Policy) { + case item_hscroll_policy::unbound: + return{ std::numeric_limits::min(), std::numeric_limits::max()}; + case item_hscroll_policy::cling_to_edge: return{ 1 - ItemLength, TextAreaWidth - 1 }; @@ -359,9 +368,15 @@ namespace bool were_right_items() { return MinRightOffset != NonValue; } }; - int get_item_smart_hpos(const int NewHPos, const int ItemLength, const int TextAreaWidth) + int get_item_absolute_hpos(const int NewHPos, const int ItemLength, const int TextAreaWidth, const item_hscroll_policy Policy) { - return NewHPos >= 0 ? NewHPos : TextAreaWidth - ItemLength + NewHPos + 1; + const auto HPosLimits{ item_hpos_limits(ItemLength, TextAreaWidth, Policy) }; + return std::clamp(NewHPos, HPosLimits.first, HPosLimits.second); + } + + int get_item_smart_hpos(const int NewHPos, const int ItemLength, const int TextAreaWidth, const item_hscroll_policy Policy) + { + return get_item_absolute_hpos(NewHPos >= 0 ? NewHPos : TextAreaWidth - ItemLength + NewHPos + 1, ItemLength, TextAreaWidth, Policy); } bool set_item_hpos( @@ -373,10 +388,10 @@ namespace { if (Item.Flags & LIF_SEPARATOR) return false; - const auto ItemLength{ static_cast(ShowAmpersand ? visual_string_length(Item.Name) : HiStrlen(Item.Name)) }; + const auto ItemLength{ get_item_visual_length(ShowAmpersand, Item.Name) }; if (ItemLength <= 0) return false; - const auto HPosLimits{ item_hpos_limits(Policy, ItemLength, TextAreaWidth) }; + const auto HPosLimits{ item_hpos_limits(ItemLength, TextAreaWidth, Policy) }; const auto ClampedHPos = std::clamp(GetNewHPos(ItemLength), HPosLimits.first, HPosLimits.second); if (Item.HPos == ClampedHPos) @@ -411,7 +426,7 @@ namespace { if (Item.Flags & LIF_SEPARATOR) continue; - const auto ItemLength{ static_cast(ShowAmpersand ? visual_string_length(Item.Name) : HiStrlen(Item.Name)) }; + const auto ItemLength{ get_item_visual_length(ShowAmpersand, Item.Name) }; if (ItemLength <= 0) continue; const auto NewHPos{ GetNewHPos(Item) }; @@ -829,10 +844,9 @@ int VMenu::AddItem(MenuItemEx&& NewItem,int PosAdd) if (PosAdd <= SelectPos) SelectPos++; - if (CheckFlags(VMENU_SHOWAMPERSAND)) - UpdateMaxLength(visual_string_length(NewMenuItem.Name)); - else - UpdateMaxLength(HiStrlen(NewMenuItem.Name)); + const auto ItemLength{ get_item_visual_length(CheckFlags(VMENU_SHOWAMPERSAND), NewMenuItem.Name) }; + UpdateMaxLength(ItemLength); + UpdateAllItemsBoundaries(NewMenuItem.HPos, ItemLength); const auto NewFlags = NewMenuItem.Flags; NewMenuItem.Flags = 0; @@ -860,10 +874,9 @@ bool VMenu::UpdateItem(const FarListUpdate *NewItem) UpdateItemFlags(NewItem->Index, NewItem->Item.Flags); Item.SimpleUserData = NewItem->Item.UserData; - if (CheckFlags(VMENU_SHOWAMPERSAND)) - UpdateMaxLength(visual_string_length(Item.Name)); - else - UpdateMaxLength(HiStrlen(Item.Name)); + const auto ItemLength{ get_item_visual_length(CheckFlags(VMENU_SHOWAMPERSAND), Item.Name) }; + UpdateMaxLength(ItemLength); + UpdateAllItemsBoundaries(Item.HPos, ItemLength); SetMenuFlags(VMENU_UPDATEREQUIRED | (bFilterEnabled ? VMENU_REFILTERREQUIRED : VMENU_NONE)); @@ -934,6 +947,7 @@ void VMenu::clear() TopPos=0; m_MaxItemLength = 0; UpdateMaxLengthFromTitles(); + ResetAllItemsBoundaries(); SetMenuFlags(VMENU_UPDATEREQUIRED); } @@ -2175,35 +2189,125 @@ int VMenu::VisualPosToReal(int VPos) const return ItemIterator != Items.cend()? ItemIterator - Items.cbegin() : -1; } +//bool VMenu::SetItemHPos(MenuItemEx& Item, int NewHPos, int ItemLength, int TextAreaWidth, item_hscroll_policy Policy) +//{ +// if (Item.Flags & LIF_SEPARATOR) return false; +// +// const auto ItemLength{ get_item_visual_length(ShowAmpersand, Item.Name) }; +// if (ItemLength <= 0) return false; +// +// const auto HPosLimits{ item_hpos_limits(Policy, ItemLength, TextAreaWidth) }; +// const auto ClampedHPos = std::clamp(GetNewHPos(ItemLength), HPosLimits.first, HPosLimits.second); +// +// if (Item.HPos == ClampedHPos) +// return false; +// +// Item.HPos = ClampedHPos; +// return true; +//} + +bool VMenu::SetAllItemsHPos(auto const GetNewHPos, int const TextAreaWidth) +{ + bool NeedRedraw{}; + ResetAllItemsBoundaries(); + + for (auto& Item : Items) + { + if (Item.Flags & LIF_SEPARATOR) continue; + + const auto ItemLength{ get_item_visual_length(CheckFlags(VMENU_SHOWAMPERSAND), Item.Name) }; + if (ItemLength <= 0) continue; + + const auto NewHPos = [&]() -> int + { + if constexpr (std::is_invocable_r_v) + return GetNewHPos(Item); + if constexpr (std::is_invocable_r_v) + return GetNewHPos(ItemLength); + if constexpr (std::is_invocable_r_v) + return GetNewHPos(Item.HPos, ItemLength); + }(); + + UpdateAllItemsBoundaries(NewHPos, ItemLength); + + if (Item.HPos == NewHPos) continue; + + Item.HPos = NewHPos; + NeedRedraw = true; + } + + if (NeedRedraw) SetMenuFlags(VMENU_UPDATEREQUIRED); + return NeedRedraw; +} + bool VMenu::SetAllItemsSmartHPos(const int NewHPos) { const auto TextAreaWidth{ CalculateTextAreaWidth() }; if (TextAreaWidth <= 0) return false; - EnBlocHScrollMode = false; - EnBlocHScrollDiscriminator = std::nullopt; + const auto Policy{ CheckFlags(VMENU_ENABLEALIGNANNOTATIONS) ? item_hscroll_policy::unbound : item_hscroll_policy::bound_stick_to_left }; - const auto Policy{ CheckFlags(VMENU_ENABLEALIGNANNOTATIONS) ? item_hscroll_policy::cling_to_edge : item_hscroll_policy::bound_stick_to_left }; + return SetAllItemsHPos( + [=](const int ItemLength) + { + return get_item_smart_hpos(NewHPos, ItemLength, TextAreaWidth, Policy); + }, + TextAreaWidth); +} - bool NeedRedraw{}; +bool VMenu::ShiftAllItemsHPos(const int Shift) +{ + const auto TextAreaWidth{ CalculateTextAreaWidth() }; + if (TextAreaWidth <= 0) return false; - for (auto& Item : Items) - { - if (set_item_hpos( - Item, - [=](int ItemLength) { return get_item_smart_hpos(NewHPos, ItemLength, TextAreaWidth); }, - CheckFlags(VMENU_SHOWAMPERSAND), - TextAreaWidth, - Policy)) + // TBD: Make a testable function + const auto AdjustedShift = [&] { - NeedRedraw = true; - } - } + if (m_AllItemsBoundaries.second <= 0) + return Shift <= 0 ? 0 : Shift - m_AllItemsBoundaries.second; + if (m_AllItemsBoundaries.first >= TextAreaWidth) + return Shift >= 0 ? 0 : Shift - (m_AllItemsBoundaries.first - TextAreaWidth); + if (Shift <= 0) + return std::max(Shift, 1 - m_AllItemsBoundaries.second); + if (Shift >= 0) + return std::min(Shift, TextAreaWidth - m_AllItemsBoundaries.first - 1); + return Shift; + }(); - if (NeedRedraw) SetMenuFlags(VMENU_UPDATEREQUIRED); - return NeedRedraw; + if (!AdjustedShift) return false; + + const auto Policy{ CheckFlags(VMENU_ENABLEALIGNANNOTATIONS) ? item_hscroll_policy::unbound : item_hscroll_policy::bound_stick_to_left }; + + return SetAllItemsHPos( + [=](const int ItemHPos, const int ItemLength) + { + return get_item_absolute_hpos(ItemHPos + AdjustedShift, ItemLength, TextAreaWidth, Policy); + }, + TextAreaWidth); } +bool VMenu::AlignAnnotations() +{ + if (!CheckFlags(VMENU_ENABLEALIGNANNOTATIONS)) return false; + + const auto TextAreaWidth{ CalculateTextAreaWidth() }; + if (TextAreaWidth <= 0 || TextAreaWidth + 2 <= 0) return false; + const auto AlignPos{ (TextAreaWidth + 2) / 4 }; + + return SetAllItemsHPos( + [=](const MenuItemEx& Item) + { + return Item.Annotations.empty() ? 0 : AlignPos - Item.Annotations.front().first; + }, + TextAreaWidth); +} + + + + + + + bool VMenu::ShiftAllItemsHPosLimited(const int Shift, const int TextAreaWidth) { const auto Policy{ CheckFlags(VMENU_ENABLEALIGNANNOTATIONS) ? item_hscroll_policy::cling_to_edge : item_hscroll_policy::bound_stick_to_left }; @@ -2228,95 +2332,64 @@ bool VMenu::ShiftAllItemsHPosLimited(const int Shift, const int TextAreaWidth) bool VMenu::SetCurItemSmartHPos(const int NewHPos) { - const auto TextAreaWidth{ CalculateTextAreaWidth() }; - if (TextAreaWidth <= 0) return false; + //const auto TextAreaWidth{ CalculateTextAreaWidth() }; + //if (TextAreaWidth <= 0) return false; - const auto Policy{ CheckFlags(VMENU_ENABLEALIGNANNOTATIONS) ? item_hscroll_policy::cling_to_edge : item_hscroll_policy::bound_stick_to_left }; + //const auto Policy{ CheckFlags(VMENU_ENABLEALIGNANNOTATIONS) ? item_hscroll_policy::cling_to_edge : item_hscroll_policy::bound_stick_to_left }; - auto& Item{ Items[SelectPos] }; + //auto& Item{ Items[SelectPos] }; - if (set_item_hpos( - Item, - [=](int ItemLength) { return get_item_smart_hpos(NewHPos, ItemLength, TextAreaWidth); }, - CheckFlags(VMENU_SHOWAMPERSAND), - TextAreaWidth, - Policy)) - { - SetMenuFlags(VMENU_UPDATEREQUIRED); - return true; - } + //if (set_item_hpos( + // Item, + // [=](int ItemLength) { return get_item_smart_hpos(NewHPos, ItemLength, TextAreaWidth); }, + // CheckFlags(VMENU_SHOWAMPERSAND), + // TextAreaWidth, + // Policy)) + //{ + // SetMenuFlags(VMENU_UPDATEREQUIRED); + // return true; + //} return false; } bool VMenu::ShiftCurItemHPos(const int Shift) { - const auto TextAreaWidth{ CalculateTextAreaWidth() }; - if (TextAreaWidth <= 0) return false; + //const auto TextAreaWidth{ CalculateTextAreaWidth() }; + //if (TextAreaWidth <= 0) return false; - const auto Policy{ CheckFlags(VMENU_ENABLEALIGNANNOTATIONS) ? item_hscroll_policy::cling_to_edge : item_hscroll_policy::bound_stick_to_left }; + //const auto Policy{ CheckFlags(VMENU_ENABLEALIGNANNOTATIONS) ? item_hscroll_policy::cling_to_edge : item_hscroll_policy::bound_stick_to_left }; - auto& Item{ Items[SelectPos] }; + //auto& Item{ Items[SelectPos] }; - if (set_item_hpos( - Item, - [&](int) { return Item.HPos + Shift; }, - CheckFlags(VMENU_SHOWAMPERSAND), - TextAreaWidth, - Policy)) - { - SetMenuFlags(VMENU_UPDATEREQUIRED); - return true; - } + //if (set_item_hpos( + // Item, + // [&](int) { return Item.HPos + Shift; }, + // CheckFlags(VMENU_SHOWAMPERSAND), + // TextAreaWidth, + // Policy)) + //{ + // SetMenuFlags(VMENU_UPDATEREQUIRED); + // return true; + //} return false; } -bool VMenu::AlignAnnotations() -{ - const auto TextAreaWidth{ CalculateTextAreaWidth() }; - if (TextAreaWidth <= 0 || TextAreaWidth + 2 <= 0) return false; - const auto AlignPos{ (TextAreaWidth + 2) / 4 }; - - const auto[NeedRedraw, HScrollDiscriminator] = set_all_items_en_bloc_hpos( - Items, - [=](const auto& Item) { return Item.Annotations.empty() ? 0 : AlignPos - Item.Annotations.front().first; }, - CheckFlags(VMENU_SHOWAMPERSAND)); - - EnBlocHScrollMode = true; - EnBlocHScrollDiscriminator = HScrollDiscriminator; - - if (NeedRedraw) SetMenuFlags(VMENU_UPDATEREQUIRED); - return NeedRedraw; -} - bool VMenu::ShiftAllItemsHPosEnBlock(const int Shift, const int TextAreaWidth) { - const auto AdjustedShift = EnBlocHScrollDiscriminator - ? adjust_en_bloc_shift(Shift, EnBlocHScrollDiscriminator.value(), TextAreaWidth) - : Shift; - if (!AdjustedShift) return false; - - const auto [NeedRedraw, HScrollDiscriminator] = set_all_items_en_bloc_hpos( - Items, - [=](const auto& Item) { return Item.HPos + AdjustedShift; }, - CheckFlags(VMENU_SHOWAMPERSAND)); + //const auto AdjustedShift = EnBlocHScrollDiscriminator + // ? adjust_en_bloc_shift(Shift, EnBlocHScrollDiscriminator.value(), TextAreaWidth) + // : Shift; + //if (!AdjustedShift) return false; - EnBlocHScrollDiscriminator = HScrollDiscriminator; - return NeedRedraw; -} - -bool VMenu::ShiftAllItemsHPos(const int Shift) -{ - const auto TextAreaWidth{ CalculateTextAreaWidth() }; - if (TextAreaWidth <= 0) return false; + //const auto [NeedRedraw, HScrollDiscriminator] = set_all_items_en_bloc_hpos( + // Items, + // [=](const auto& Item) { return Item.HPos + AdjustedShift; }, + // CheckFlags(VMENU_SHOWAMPERSAND)); - const auto ShiftAllItemsHPosFunc{ EnBlocHScrollMode ? &VMenu::ShiftAllItemsHPosEnBlock : &VMenu::ShiftAllItemsHPosLimited }; - if ((this->*ShiftAllItemsHPosFunc)(Shift, TextAreaWidth)) - { - SetMenuFlags(VMENU_UPDATEREQUIRED); - return true; - } + //EnBlocHScrollDiscriminator = HScrollDiscriminator; + //return NeedRedraw; return false; } @@ -2332,7 +2405,7 @@ void VMenu::Show() if (!CheckFlags(VMENU_COMBOBOX)) { - const auto VisibleMaxItemLength = std::min(static_cast(ScrX) > ServiceAreaSize? ScrX - ServiceAreaSize : 0, m_MaxItemLength); + const auto VisibleMaxItemLength = std::min(ScrX > ServiceAreaSize? ScrX - ServiceAreaSize : 0, m_MaxItemLength); const auto MenuWidth = ServiceAreaSize + VisibleMaxItemLength; bool AutoCenter = false; @@ -2903,12 +2976,26 @@ bool VMenu::CheckKeyHiOrAcc(DWORD Key, int Type, bool Translate, bool ChangePos, void VMenu::UpdateMaxLengthFromTitles() { //тайтл + 2 пробела вокруг - UpdateMaxLength(std::max(strTitle.size(), strBottomTitle.size()) + 2); + UpdateMaxLength(static_cast(std::max(strTitle.size(), strBottomTitle.size()) + 2)); +} + +void VMenu::UpdateMaxLength(int const ItemLength) +{ + m_MaxItemLength = std::max(m_MaxItemLength, ItemLength); } -void VMenu::UpdateMaxLength(size_t const Length) +void VMenu::ResetAllItemsBoundaries() { - m_MaxItemLength = std::max(m_MaxItemLength, Length); + m_AllItemsBoundaries = { std::numeric_limits::max(), std::numeric_limits::min() }; +} + +void VMenu::UpdateAllItemsBoundaries(int const ItemHPos, int const ItemLength) +{ + m_AllItemsBoundaries = + { + std::min(m_AllItemsBoundaries.first, ItemHPos), + std::max(m_AllItemsBoundaries.second, ItemHPos + ItemLength) + }; } void VMenu::SetMaxHeight(int NewMaxHeight) @@ -2936,7 +3023,7 @@ void VMenu::SetBottomTitle(string_view const BottomTitle) strBottomTitle = BottomTitle; - UpdateMaxLength(strBottomTitle.size() + 2); + UpdateMaxLength(static_cast(strBottomTitle.size() + 2)); } void VMenu::SetTitle(string_view const Title) @@ -2945,7 +3032,7 @@ void VMenu::SetTitle(string_view const Title) strTitle = Title; - UpdateMaxLength(strTitle.size() + 2); + UpdateMaxLength(static_cast(strTitle.size() + 2)); } void VMenu::ResizeConsole() @@ -3137,7 +3224,7 @@ bool VMenu::GetVMenuInfo(FarListInfo* Info) const Info->MaxHeight = MaxHeight; // BUGBUG const auto ServiceAreaSize = menu_layout::get_service_area_size(*this); - if (static_cast(m_Where.width()) > ServiceAreaSize) + if (m_Where.width() > ServiceAreaSize) Info->MaxLength = m_Where.width() - ServiceAreaSize; else Info->MaxLength = 0; @@ -3319,7 +3406,7 @@ std::vector VMenu::AddHotkeys(std::span const MenuItems) size_t VMenu::GetNaturalMenuWidth() const { - return m_MaxItemLength + menu_layout::get_service_area_size(*this); + return static_cast(m_MaxItemLength) + menu_layout::get_service_area_size(*this); } void VMenu::EnableFilter(bool const Enable) @@ -3494,7 +3581,7 @@ TEST_CASE("item.hpos.limits") { int ItemLength; int TextAreaWidth; - // unbound, cling_to_edge, bound, bound_stick_to_left + // cling_to_edge, bound, bound_stick_to_left std::initializer_list> Expected; } TestDataPoints[] = { @@ -3513,11 +3600,14 @@ TEST_CASE("item.hpos.limits") for (const auto& TestDataPoint : TestDataPoints) { + REQUIRE(std::pair{ std::numeric_limits::min(), std::numeric_limits::max() } + == item_hpos_limits(TestDataPoint.ItemLength, TestDataPoint.TextAreaWidth, item_hscroll_policy::unbound)); + for (const auto& Policy : { item_hscroll_policy::cling_to_edge, item_hscroll_policy::bound, item_hscroll_policy::bound_stick_to_left }) { REQUIRE(std::ranges::data(TestDataPoint.Expected)[std::to_underlying(Policy)] - == item_hpos_limits(Policy, TestDataPoint.ItemLength, TestDataPoint.TextAreaWidth)); + == item_hpos_limits(TestDataPoint.ItemLength, TestDataPoint.TextAreaWidth, Policy)); } } } diff --git a/far/vmenu.hpp b/far/vmenu.hpp index cfc1176d92..1cc3e0e001 100644 --- a/far/vmenu.hpp +++ b/far/vmenu.hpp @@ -297,6 +297,9 @@ class VMenu final: public Modal int CheckHighlights(wchar_t CheckSymbol,int StartPos=0) const; wchar_t GetHighlights(const MenuItemEx *Item) const; + //[[nodiscard]] bool SetItemHPos(MenuItemEx& Item, int NewHPos, int ItemLength, int TextAreaWidth, item_hscroll_policy Policy); + [[nodiscard]] bool SetAllItemsHPos(auto GetNewHPos, int TextAreaWidth); + [[nodiscard]] bool SetAllItemsSmartHPos(int NewHPos); [[nodiscard]] bool SetCurItemSmartHPos(int NewHPos); [[nodiscard]] bool ShiftAllItemsHPos(int Shift); @@ -308,13 +311,14 @@ class VMenu final: public Modal - //[[nodiscard]] bool SetItemAbsoluteHPos(MenuItemEx& Item, int NewHPos, int ItemLength, int TextAreaWidth, item_hscroll_policy Policy); //[[nodiscard]] bool SetItemAbsoluteHPos(MenuItemEx& Item, int NewHPos, int TextAreaWidth, item_hscroll_policy Policy); // Negative NewHPos is relative to the right edge, e.i., -1 aligns the item to the right //[[nodiscard]] bool SetItemSmartHPos(MenuItemEx& Item, int NewHPos, int TextAreaWidth, item_hscroll_policy Policy); void UpdateMaxLengthFromTitles(); - void UpdateMaxLength(size_t Length); + void UpdateMaxLength(int ItemLength); + void ResetAllItemsBoundaries(); + void UpdateAllItemsBoundaries(int ItemHPos, int ItemLength); bool ShouldSendKeyToFilter(unsigned Key) const; //корректировка текущей позиции и флагов SELECTED void UpdateSelectPos(); @@ -330,7 +334,8 @@ class VMenu final: public Modal int TopPos{}; int MaxHeight; bool WasAutoHeight{}; - size_t m_MaxItemLength{}; + int m_MaxItemLength{}; + std::pair m_AllItemsBoundaries{}; window_ptr CurrentWindow; bool PrevCursorVisible{}; size_t PrevCursorSize{}; @@ -347,8 +352,6 @@ class VMenu final: public Modal intptr_t ItemSubMenusCount{}; vmenu_colors_t Colors{}; bool bRightBtnPressed{}; - bool EnBlocHScrollMode{}; - std::optional EnBlocHScrollDiscriminator{}; UUID MenuId; };