-
Notifications
You must be signed in to change notification settings - Fork 207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-802: Macro API to report VMenu alignment state. Part 1: Backend implementation. #895
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
6410 | ||
6411 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -181,9 +181,28 @@ struct menu_layout | |
} | ||
}; | ||
|
||
enum class item_hscroll_policy | ||
{ | ||
unbound, // The item can move freely beyond window edges. | ||
cling_to_edge, // The item can move beyond window edges, but at least one character is always visible. | ||
bound, // The item can move only within the window boundaries. | ||
bound_stick_to_left // Like bound, but if the item shorter than TextAreaWidth, it is always attached to the left window edge. | ||
}; | ||
|
||
namespace | ||
{ | ||
MenuItemEx far_list_to_menu_item(const FarListItem& FItem) | ||
const FarListItem string_to_far_list_item(const wchar_t* NewStrItem) | ||
{ | ||
if (!NewStrItem) | ||
return { .Flags{ LIF_SEPARATOR } }; | ||
|
||
if (NewStrItem[0] == 0x1) | ||
return { .Flags{ LIF_SEPARATOR }, .Text{ NewStrItem + 1 } }; | ||
|
||
return { .Text{ NewStrItem } }; | ||
} | ||
|
||
MenuItemEx far_list_item_to_menu_item(const FarListItem& FItem) | ||
{ | ||
MenuItemEx Result; | ||
Result.Flags = FItem.Flags; | ||
|
@@ -291,14 +310,6 @@ namespace | |
return static_cast<int>(ShowAmpersand ? visual_string_length(ItemName) : HiStrlen(ItemName)); | ||
} | ||
|
||
enum class item_hscroll_policy | ||
{ | ||
unbound, // The item can move freely beyond window edges. | ||
cling_to_edge, // The item can move beyond window edges, but at least one character is always visible. | ||
bound, // The item can move only within the window boundaries. | ||
bound_stick_to_left // Like bound, but if the item shorter than TextAreaWidth, it is always attached to the left window edge. | ||
}; | ||
|
||
std::pair<int, int> item_hpos_limits(const int ItemLength, const int TextAreaWidth, const item_hscroll_policy Policy) noexcept | ||
{ | ||
using enum item_hscroll_policy; | ||
|
@@ -705,7 +716,7 @@ int VMenu::InsertItem(const FarListInsert *NewItem) | |
{ | ||
if (NewItem) | ||
{ | ||
if (AddItem(far_list_to_menu_item(NewItem->Item), NewItem->Index) >= 0) | ||
if (AddItem(far_list_item_to_menu_item(NewItem->Item), NewItem->Index) >= 0) | ||
return static_cast<int>(Items.size()); | ||
} | ||
|
||
|
@@ -718,7 +729,7 @@ int VMenu::AddItem(const FarList* List) | |
{ | ||
for (const auto& Item: std::span(List->Items, List->ItemsNumber)) | ||
{ | ||
AddItem(far_list_to_menu_item(Item)); | ||
AddItem(far_list_item_to_menu_item(Item)); | ||
} | ||
} | ||
|
||
|
@@ -727,22 +738,7 @@ int VMenu::AddItem(const FarList* List) | |
|
||
int VMenu::AddItem(const wchar_t *NewStrItem) | ||
{ | ||
FarListItem FarListItem0{}; | ||
|
||
if (!NewStrItem || NewStrItem[0] == 0x1) | ||
{ | ||
FarListItem0.Flags=LIF_SEPARATOR; | ||
if (NewStrItem) | ||
FarListItem0.Text = NewStrItem + 1; | ||
} | ||
else | ||
{ | ||
FarListItem0.Text=NewStrItem; | ||
} | ||
|
||
const FarList List{ sizeof(List), 1, &FarListItem0 }; | ||
|
||
return AddItem(&List) - 1; //-1 потому что AddItem(FarList) возвращает количество элементов | ||
return AddItem(far_list_item_to_menu_item(string_to_far_list_item(NewStrItem))); | ||
} | ||
|
||
int VMenu::AddItem(MenuItemEx&& NewItem,int PosAdd) | ||
|
@@ -761,7 +757,7 @@ int VMenu::AddItem(MenuItemEx&& NewItem,int PosAdd) | |
|
||
const auto ItemLength{ get_item_visual_length(CheckFlags(VMENU_SHOWAMPERSAND), NewMenuItem.Name) }; | ||
UpdateMaxLength(ItemLength); | ||
UpdateAllItemsBoundaries(NewMenuItem.HorizontalPosition, ItemLength); | ||
m_HorizontalTracker.add_item(NewMenuItem.HorizontalPosition, ItemLength, NewMenuItem.SafeGetFirstAnnotation()); | ||
|
||
const auto NewFlags = NewMenuItem.Flags; | ||
NewMenuItem.Flags = 0; | ||
|
@@ -778,20 +774,22 @@ bool VMenu::UpdateItem(const FarListUpdate *NewItem) | |
return false; | ||
|
||
auto& Item = Items[NewItem->Index]; | ||
m_HorizontalTracker.remove_item(Item.HorizontalPosition, get_item_visual_length(CheckFlags(VMENU_SHOWAMPERSAND), Item.Name), Item.SafeGetFirstAnnotation()); | ||
|
||
// Освободим память... от ранее занятого ;-) | ||
if (NewItem->Item.Flags&LIF_DELETEUSERDATA) | ||
{ | ||
Item.ComplexUserData = {}; | ||
} | ||
Item.Annotations.clear(); | ||
|
||
Item.Name = NullToEmpty(NewItem->Item.Text); | ||
UpdateItemFlags(NewItem->Index, NewItem->Item.Flags); | ||
Item.SimpleUserData = NewItem->Item.UserData; | ||
|
||
const auto ItemLength{ get_item_visual_length(CheckFlags(VMENU_SHOWAMPERSAND), Item.Name) }; | ||
UpdateMaxLength(ItemLength); | ||
UpdateAllItemsBoundaries(Item.HorizontalPosition, ItemLength); | ||
m_HorizontalTracker.add_item(Item.HorizontalPosition, ItemLength, Item.SafeGetFirstAnnotation()); | ||
|
||
SetMenuFlags(VMENU_UPDATEREQUIRED | (bFilterEnabled ? VMENU_REFILTERREQUIRED : VMENU_NONE)); | ||
|
||
|
@@ -816,19 +814,23 @@ int VMenu::DeleteItem(int ID, int Count) | |
return static_cast<int>(Items.size()); | ||
} | ||
|
||
for (const auto I: std::views::iota(0, Count)) | ||
for (const auto& I : std::span{ Items.cbegin() + ID, static_cast<size_t>(Count) }) | ||
{ | ||
if (Items[ID+I].Flags & MIF_SUBMENU) | ||
if (I.Flags & MIF_SUBMENU) | ||
--ItemSubMenusCount; | ||
|
||
if (!item_is_visible(Items[ID+I])) | ||
if (!item_is_visible(I)) | ||
--ItemHiddenCount; | ||
|
||
m_HorizontalTracker.remove_item(I.HorizontalPosition, get_item_visual_length(CheckFlags(VMENU_SHOWAMPERSAND), I.Name), I.SafeGetFirstAnnotation()); | ||
} | ||
|
||
// а вот теперь перемещения | ||
const auto FirstIter = Items.begin() + ID, LastIter = FirstIter + Count; | ||
if (Items.size() > 1) | ||
{ | ||
const auto FirstIter = Items.begin() + ID, LastIter = FirstIter + Count; | ||
Items.erase(FirstIter, LastIter); | ||
} | ||
|
||
// коррекция текущей позиции | ||
if (SelectPos >= ID && SelectPos < ID+Count) | ||
|
@@ -862,7 +864,7 @@ void VMenu::clear() | |
TopPos=0; | ||
m_MaxItemLength = 0; | ||
UpdateMaxLengthFromTitles(); | ||
ResetAllItemsBoundaries(); | ||
m_HorizontalTracker.reset(); | ||
|
||
SetMenuFlags(VMENU_UPDATEREQUIRED); | ||
} | ||
|
@@ -2121,11 +2123,11 @@ bool VMenu::SetItemHPos(MenuItemEx& Item, const auto& GetNewHPos) | |
return GetNewHPos(Item.HorizontalPosition, ItemLength); | ||
}(); | ||
|
||
UpdateAllItemsBoundaries(NewHPos, ItemLength); | ||
m_HorizontalTracker.update_item_hpos(Item.HorizontalPosition , NewHPos, ItemLength, Item.SafeGetFirstAnnotation()); | ||
|
||
if (Item.HorizontalPosition == NewHPos) return false; | ||
|
||
Item.HorizontalPosition = NewHPos; | ||
|
||
return true; | ||
} | ||
|
||
|
@@ -2167,7 +2169,6 @@ bool VMenu::ShiftCurItemHPos(const int Shift) | |
|
||
bool VMenu::SetAllItemsHPos(const auto& GetNewHPos) | ||
{ | ||
ResetAllItemsBoundaries(); | ||
bool NeedRedraw{}; | ||
|
||
for (auto& Item : Items) | ||
|
@@ -2184,6 +2185,14 @@ bool VMenu::SetAllItemsSmartHPos(const int NewHPos) | |
|
||
const auto Policy{ CheckFlags(VMENU_ENABLEALIGNANNOTATIONS) ? item_hscroll_policy::unbound : item_hscroll_policy::bound_stick_to_left }; | ||
|
||
const auto Cookie{ [&]() | ||
{ | ||
if (NewHPos >= 0) | ||
return m_HorizontalTracker.start_bulk_update(vmenu_horizontal_tracker::alignment::Left, NewHPos, TextAreaWidth, Policy); | ||
|
||
return m_HorizontalTracker.start_bulk_update( | ||
vmenu_horizontal_tracker::alignment::Right, TextAreaWidth + NewHPos + 1, TextAreaWidth, Policy); | ||
}() }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIFE looks somewhat complicated here, maybe ternary operator? const auto Cookie = NewHPos >= 0?
m_HorizontalTracker.start_bulk_update(vmenu_horizontal_tracker::alignment::Left, ...) :
m_HorizontalTracker.start_bulk_update(vmenu_horizontal_tracker::alignment::Right, ...); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, it requires copy constructor:
What do you think of this?
And it reminded me that the cookie should be either non-movable or should properly implement move operations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Looks like it's a compiler bug 😆 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
return SetAllItemsHPos( | ||
[&](const int ItemLength) { return get_item_smart_hpos(NewHPos, ItemLength, TextAreaWidth, Policy); }); | ||
} | ||
|
@@ -2193,11 +2202,14 @@ bool VMenu::ShiftAllItemsHPos(const int Shift) | |
const auto TextAreaWidth{ CalculateTextAreaWidth() }; | ||
if (TextAreaWidth <= 0) return false; | ||
|
||
const auto AdjustedShift{ adjust_hpos_shift(Shift, m_AllItemsBoundaries.first, m_AllItemsBoundaries.second, TextAreaWidth) }; | ||
const auto AdjustedShift{ adjust_hpos_shift(Shift, m_HorizontalTracker.m_LBoundary, m_HorizontalTracker.m_RBoundary, TextAreaWidth) }; | ||
if (!AdjustedShift) return false; | ||
|
||
const auto Policy{ CheckFlags(VMENU_ENABLEALIGNANNOTATIONS) ? item_hscroll_policy::unbound : item_hscroll_policy::bound_stick_to_left }; | ||
|
||
const auto Cookie{ | ||
m_HorizontalTracker.start_bulk_update( | ||
m_HorizontalTracker.m_Alignment, m_HorizontalTracker.m_AlignedAtColumn + AdjustedShift, TextAreaWidth, Policy) }; | ||
return SetAllItemsHPos( | ||
[&](const int ItemHPos, const int ItemLength) { return get_item_absolute_hpos(ItemHPos + AdjustedShift, ItemLength, TextAreaWidth, Policy); }); | ||
} | ||
|
@@ -2210,8 +2222,10 @@ bool VMenu::AlignAnnotations() | |
if (TextAreaWidth <= 0) return false; | ||
const auto AlignPos{ (TextAreaWidth + 2) / 4 }; | ||
|
||
const auto Cookie{ | ||
m_HorizontalTracker.start_bulk_update(vmenu_horizontal_tracker::alignment::Annotation, AlignPos, TextAreaWidth, item_hscroll_policy::unbound) }; | ||
return SetAllItemsHPos( | ||
[&](const MenuItemEx& Item) { return Item.Annotations.empty() ? 0 : AlignPos - Item.Annotations.front().first; }); | ||
[&](const MenuItemEx& Item) { return AlignPos - Item.SafeGetFirstAnnotation(); }); | ||
} | ||
|
||
void VMenu::Show() | ||
|
@@ -2487,9 +2501,25 @@ void VMenu::DrawTitles() const | |
|
||
if constexpr ((false)) | ||
{ | ||
const auto AlignmentMark{ [](vmenu_horizontal_tracker::alignment Alignment) | ||
{ | ||
switch (Alignment) | ||
{ | ||
case vmenu_horizontal_tracker::alignment::Left: return L'<'; | ||
case vmenu_horizontal_tracker::alignment::Right: return L'>'; | ||
case vmenu_horizontal_tracker::alignment::Annotation: return L'^'; | ||
default: std::unreachable(); | ||
} | ||
} }; | ||
|
||
set_color(Colors, color_indices::Title); | ||
|
||
const auto AllItemsBoundariesLabel{ std::format(L" [{}, {}] ", m_AllItemsBoundaries.first, m_AllItemsBoundaries.second) }; | ||
const auto AllItemsBoundariesLabel{ std::format(L" [{}:{} {} {} {}] ", | ||
m_HorizontalTracker.m_LBoundary, | ||
m_HorizontalTracker.m_RBoundary, | ||
AlignmentMark(m_HorizontalTracker.m_Alignment), | ||
m_HorizontalTracker.m_AlignedAtColumn, | ||
m_HorizontalTracker.m_StrayItems) }; | ||
GotoXY(m_Where.left + 2, m_Where.bottom); | ||
Text(AllItemsBoundariesLabel); | ||
|
||
|
@@ -2825,18 +2855,76 @@ void VMenu::UpdateMaxLength(int const ItemLength) | |
m_MaxItemLength = std::max(m_MaxItemLength, ItemLength); | ||
} | ||
|
||
void VMenu::ResetAllItemsBoundaries() | ||
bool vmenu_horizontal_tracker::is_item_aligned(int const ItemHPos, int const ItemLength, int const ItemAnnotationPos) | ||
{ | ||
m_AllItemsBoundaries = { std::numeric_limits<int>::max(), std::numeric_limits<int>::min() }; | ||
const auto AnchorOffset{ [&]() | ||
{ | ||
switch (m_Alignment) | ||
{ | ||
case alignment::Left: return 0; | ||
case alignment::Right: return ItemLength; | ||
case alignment::Annotation: return ItemAnnotationPos; | ||
default: std::unreachable(); | ||
} | ||
}() }; | ||
|
||
return ItemHPos + AnchorOffset == m_AlignedAtColumn; | ||
} | ||
|
||
void VMenu::UpdateAllItemsBoundaries(int const ItemHPos, int const ItemLength) | ||
void vmenu_horizontal_tracker::update_boundaries(int const ItemHPos, int const ItemLength) | ||
{ | ||
m_AllItemsBoundaries = | ||
m_LBoundary = std::min(m_LBoundary, ItemHPos); | ||
m_RBoundary = std::max(m_RBoundary, ItemHPos + ItemLength); | ||
} | ||
|
||
void vmenu_horizontal_tracker::add_item(int const ItemHPos, int const ItemLength, int const ItemAnnotationPos) | ||
{ | ||
update_boundaries(ItemHPos, ItemLength); | ||
|
||
if (!is_item_aligned(ItemHPos, ItemLength, ItemAnnotationPos)) | ||
m_StrayItems++; | ||
} | ||
|
||
void vmenu_horizontal_tracker::remove_item(int const ItemHPos, int const ItemLength, int const ItemAnnotationPos) | ||
{ | ||
if (!is_item_aligned(ItemHPos, ItemLength, ItemAnnotationPos)) | ||
m_StrayItems--; | ||
} | ||
|
||
vmenu_horizontal_tracker::cookie vmenu_horizontal_tracker::start_bulk_update( | ||
alignment const Alignment, int const AlignedAtColumn, int const TextAreaWidth, item_hscroll_policy const Policy) | ||
{ | ||
reset(); | ||
|
||
m_IsBulkUpdate = true; | ||
m_Alignment = Alignment; | ||
|
||
if (m_Alignment == alignment::Annotation || Policy == item_hscroll_policy::unbound) | ||
{ | ||
std::min(m_AllItemsBoundaries.first, ItemHPos), | ||
std::max(m_AllItemsBoundaries.second, ItemHPos + ItemLength) | ||
}; | ||
m_AlignedAtColumn = AlignedAtColumn; | ||
} | ||
else if (m_Alignment == alignment::Left) | ||
{ | ||
m_AlignedAtColumn = std::min(AlignedAtColumn, Policy == item_hscroll_policy::cling_to_edge ? TextAreaWidth - 1 : 0); | ||
} | ||
else if (m_Alignment == alignment::Right) | ||
{ | ||
m_AlignedAtColumn = std::max(AlignedAtColumn, Policy == item_hscroll_policy::cling_to_edge ? 1 : TextAreaWidth); | ||
} | ||
else | ||
{ | ||
std::unreachable(); | ||
} | ||
|
||
return cookie{ this }; | ||
} | ||
|
||
void vmenu_horizontal_tracker::update_item_hpos(int const OldItemHPos, int const NewItemHPos, int const ItemLength, int const ItemAnnotationPos) | ||
{ | ||
if (!m_IsBulkUpdate) | ||
remove_item(OldItemHPos, ItemLength, ItemAnnotationPos); | ||
|
||
add_item(NewItemHPos, ItemLength, ItemAnnotationPos); | ||
} | ||
|
||
void VMenu::SetMaxHeight(int NewMaxHeight) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove const from the return value please.