Skip to content
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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions far/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
--------------------------------------------------------------------------------
MZK 2025-01-04 21:34:32-05:00 - build 6411

1. gh-802: Macro API to report VMenu alignment state; part 1. Backend implementation.

--------------------------------------------------------------------------------
drkns 2025-01-04 21:34:31+00:00 - build 6410

Expand Down
2 changes: 1 addition & 1 deletion far/vbuild.m4
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6410
6411
182 changes: 135 additions & 47 deletions far/vmenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

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.

{
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}

Expand All @@ -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));
}
}

Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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));

Expand All @@ -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)
Expand Down Expand Up @@ -862,7 +864,7 @@ void VMenu::clear()
TopPos=0;
m_MaxItemLength = 0;
UpdateMaxLengthFromTitles();
ResetAllItemsBoundaries();
m_HorizontalTracker.reset();

SetMenuFlags(VMENU_UPDATEREQUIRED);
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -2167,7 +2169,6 @@ bool VMenu::ShiftCurItemHPos(const int Shift)

bool VMenu::SetAllItemsHPos(const auto& GetNewHPos)
{
ResetAllItemsBoundaries();
bool NeedRedraw{};

for (auto& Item : Items)
Expand All @@ -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);
}() };
Copy link
Contributor

Choose a reason for hiding this comment

The 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, ...);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it requires copy constructor:

1>D:\DEV\FarManager\far\vmenu.cpp(2188,19): error C2280: 'vmenu_horizontal_tracker::cookie::cookie(const vmenu_horizontal_tracker::cookie &)': attempting to reference a deleted function
1>	const auto Cookie{
1>	                 ^ (compiling source file '/vmenu.cpp')
1>    D:\DEV\FarManager\far\vmenu.hpp(169,3):
1>    see declaration of 'vmenu_horizontal_tracker::cookie::cookie'
1>    		NONCOPYABLE(cookie);
1>    		^
1>    D:\DEV\FarManager\far\vmenu.hpp(169,3):
1>    'vmenu_horizontal_tracker::cookie::cookie(const vmenu_horizontal_tracker::cookie &)': function was explicitly deleted
1>    		NONCOPYABLE(cookie);
1>    		^

What do you think of this?

	const auto Cookie{
		m_HorizontalTracker.start_bulk_update(
			NewHPos >= 0 ? vmenu_horizontal_tracker::alignment::Left : vmenu_horizontal_tracker::alignment::Right,
			NewHPos >= 0 ? NewHPos : TextAreaWidth + NewHPos + 1,
			TextAreaWidth,
			Policy) };

And it reminded me that the cookie should be either non-movable or should properly implement move operations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it requires copy constructor

Looks like it's a compiler bug 😆
Please ignore my previous comment or use whatever compiles.

Copy link
Contributor

Choose a reason for hiding this comment

The 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); });
}
Expand All @@ -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); });
}
Expand All @@ -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()
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading