Skip to content

Commit a7c2187

Browse files
committed
PRE-MERGE #19591 Add IconPicker to New Tab Menu folders in SUI
2 parents 1a68985 + 469a6d7 commit a7c2187

21 files changed

+708
-411
lines changed
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
#include "pch.h"
5+
#include "IconPicker.h"
6+
#include "IconPicker.g.cpp"
7+
8+
#include <LibraryResources.h>
9+
#include "SegoeFluentIconList.h"
10+
#include "../../types/inc/utils.hpp"
11+
#include "../WinRTUtils/inc/Utils.h"
12+
13+
using namespace winrt;
14+
using namespace winrt::Windows::UI;
15+
using namespace winrt::Windows::UI::Xaml;
16+
using namespace winrt::Windows::UI::Xaml::Navigation;
17+
using namespace winrt::Windows::UI::Xaml::Controls;
18+
using namespace winrt::Windows::UI::Xaml::Media;
19+
using namespace winrt::Windows::Foundation;
20+
using namespace winrt::Windows::Foundation::Collections;
21+
using namespace winrt::Microsoft::UI::Xaml::Controls;
22+
23+
namespace winrt
24+
{
25+
namespace MUX = Microsoft::UI::Xaml;
26+
namespace WUX = Windows::UI::Xaml;
27+
}
28+
29+
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
30+
{
31+
static constexpr std::wstring_view HideIconValue{ L"none" };
32+
33+
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::_BuiltInIcons{ nullptr };
34+
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::_IconTypes{ nullptr };
35+
DependencyProperty IconPicker::_CurrentIconPathProperty{ nullptr };
36+
DependencyProperty IconPicker::_WindowRootProperty{ nullptr };
37+
38+
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::BuiltInIcons() noexcept
39+
{
40+
if (!_BuiltInIcons)
41+
{
42+
// lazy load the built-in icons
43+
std::vector<Editor::EnumEntry> builtInIcons;
44+
for (auto& [val, name] : s_SegoeFluentIcons)
45+
{
46+
builtInIcons.emplace_back(make<EnumEntry>(hstring{ name }, box_value(val)));
47+
}
48+
_BuiltInIcons = single_threaded_observable_vector<Editor::EnumEntry>(std::move(builtInIcons));
49+
}
50+
return _BuiltInIcons;
51+
}
52+
53+
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::IconTypes() noexcept
54+
{
55+
if (!_IconTypes)
56+
{
57+
// lazy load the icon types
58+
std::vector<Editor::EnumEntry> iconTypes;
59+
iconTypes.reserve(4);
60+
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeNone"), box_value(IconType::None)));
61+
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeFontIcon"), box_value(IconType::FontIcon)));
62+
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeEmoji"), box_value(IconType::Emoji)));
63+
iconTypes.emplace_back(make<EnumEntry>(RS_(L"IconPicker_IconTypeImage"), box_value(IconType::Image)));
64+
_IconTypes = winrt::single_threaded_observable_vector<Editor::EnumEntry>(std::move(iconTypes));
65+
}
66+
return _IconTypes;
67+
}
68+
69+
IconPicker::IconPicker()
70+
{
71+
_InitializeProperties();
72+
InitializeComponent();
73+
74+
_DeduceCurrentIconType();
75+
PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
76+
const auto propertyName{ args.PropertyName() };
77+
// "CurrentIconPath" changes are handled by _OnCurrentIconPathChanged()
78+
if (propertyName == L"CurrentIconType")
79+
{
80+
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingNoIcon" });
81+
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingBuiltInIcon" });
82+
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingEmojiIcon" });
83+
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"UsingImageIcon" });
84+
}
85+
else if (propertyName == L"CurrentBuiltInIcon")
86+
{
87+
CurrentIconPath(unbox_value<hstring>(_CurrentBuiltInIcon.EnumValue()));
88+
}
89+
else if (propertyName == L"CurrentEmojiIcon")
90+
{
91+
CurrentIconPath(CurrentEmojiIcon());
92+
}
93+
});
94+
}
95+
96+
void IconPicker::_InitializeProperties()
97+
{
98+
// Initialize any dependency properties here.
99+
// This performs a lazy load on these properties, instead of
100+
// initializing them when the DLL loads.
101+
if (!_CurrentIconPathProperty)
102+
{
103+
_CurrentIconPathProperty =
104+
DependencyProperty::Register(
105+
L"CurrentIconPath",
106+
xaml_typename<hstring>(),
107+
xaml_typename<Editor::IconPicker>(),
108+
PropertyMetadata{ nullptr, PropertyChangedCallback{ &IconPicker::_OnCurrentIconPathChanged } });
109+
}
110+
if (!_WindowRootProperty)
111+
{
112+
_WindowRootProperty =
113+
DependencyProperty::Register(
114+
L"WindowRoot",
115+
xaml_typename<IHostedInWindow>(),
116+
xaml_typename<Editor::IconPicker>(),
117+
PropertyMetadata{ nullptr });
118+
}
119+
}
120+
121+
void IconPicker::_OnCurrentIconPathChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
122+
{
123+
d.as<IconPicker>()->_DeduceCurrentIconType();
124+
}
125+
126+
safe_void_coroutine IconPicker::Icon_Click(const IInspectable&, const RoutedEventArgs&)
127+
{
128+
auto lifetime = get_strong();
129+
130+
const auto parentHwnd{ reinterpret_cast<HWND>(WindowRoot().GetHostingWindow()) };
131+
auto file = co_await OpenImagePicker(parentHwnd);
132+
if (!file.empty())
133+
{
134+
CurrentIconPath(file);
135+
}
136+
}
137+
138+
void IconPicker::BuiltInIconPicker_GotFocus(const IInspectable& sender, const RoutedEventArgs& /*e*/)
139+
{
140+
_updateIconFilter({});
141+
sender.as<AutoSuggestBox>().IsSuggestionListOpen(true);
142+
}
143+
144+
void IconPicker::BuiltInIconPicker_QuerySubmitted(const AutoSuggestBox& /*sender*/, const AutoSuggestBoxQuerySubmittedEventArgs& e)
145+
{
146+
const auto iconEntry = unbox_value_or<Editor::EnumEntry>(e.ChosenSuggestion(), nullptr);
147+
if (!iconEntry)
148+
{
149+
return;
150+
}
151+
CurrentBuiltInIcon(iconEntry);
152+
}
153+
154+
void IconPicker::BuiltInIconPicker_TextChanged(const AutoSuggestBox& sender, const AutoSuggestBoxTextChangedEventArgs& e)
155+
{
156+
if (e.Reason() != AutoSuggestionBoxTextChangeReason::UserInput)
157+
{
158+
return;
159+
}
160+
std::wstring_view filter{ sender.Text() };
161+
filter = til::trim(filter, L' ');
162+
_updateIconFilter(filter);
163+
}
164+
165+
void IconPicker::_updateIconFilter(std::wstring_view filter)
166+
{
167+
if (_iconFilter != filter)
168+
{
169+
_filteredBuiltInIcons = nullptr;
170+
_iconFilter = filter;
171+
_updateFilteredIconList();
172+
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FilteredBuiltInIconList" });
173+
}
174+
}
175+
176+
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconPicker::FilteredBuiltInIconList()
177+
{
178+
if (!_filteredBuiltInIcons)
179+
{
180+
_updateFilteredIconList();
181+
}
182+
return _filteredBuiltInIcons;
183+
}
184+
185+
void IconPicker::_updateFilteredIconList()
186+
{
187+
_filteredBuiltInIcons = BuiltInIcons();
188+
if (_iconFilter.empty())
189+
{
190+
return;
191+
}
192+
193+
// Find matching icons and populate the filtered list
194+
std::vector<Editor::EnumEntry> filtered;
195+
filtered.reserve(_filteredBuiltInIcons.Size());
196+
for (const auto& icon : _filteredBuiltInIcons)
197+
{
198+
if (til::contains_linguistic_insensitive(icon.EnumName(), _iconFilter))
199+
{
200+
filtered.emplace_back(icon);
201+
}
202+
}
203+
_filteredBuiltInIcons = winrt::single_threaded_observable_vector(std::move(filtered));
204+
}
205+
206+
void IconPicker::CurrentIconType(const Windows::Foundation::IInspectable& value)
207+
{
208+
if (_currentIconType != value)
209+
{
210+
// Switching from...
211+
if (_currentIconType && unbox_value<IconType>(_currentIconType.as<Editor::EnumEntry>().EnumValue()) == IconType::Image)
212+
{
213+
// Stash the current value of Icon. If the user
214+
// switches out of then back to IconType::Image, we want
215+
// the path that we display in the text box to remain unchanged.
216+
_lastIconPath = CurrentIconPath();
217+
}
218+
219+
// Set the member here instead of after setting Icon() below!
220+
// We have an Icon property changed handler defined for when we discard changes.
221+
// Inadvertently, that means that we call this setter again.
222+
// Setting the member here means that we early exit at the beginning of the function
223+
// because _currentIconType == value.
224+
_currentIconType = value;
225+
226+
// Switched to...
227+
switch (unbox_value<IconType>(value.as<Editor::EnumEntry>().EnumValue()))
228+
{
229+
case IconType::None:
230+
{
231+
CurrentIconPath(winrt::hstring{ HideIconValue });
232+
break;
233+
}
234+
case IconType::Image:
235+
{
236+
if (!_lastIconPath.empty())
237+
{
238+
// Conversely, if we switch to Image,
239+
// retrieve that saved value and apply it
240+
CurrentIconPath(_lastIconPath);
241+
}
242+
break;
243+
}
244+
case IconType::FontIcon:
245+
{
246+
if (_CurrentBuiltInIcon)
247+
{
248+
CurrentIconPath(unbox_value<hstring>(_CurrentBuiltInIcon.EnumValue()));
249+
}
250+
break;
251+
}
252+
case IconType::Emoji:
253+
{
254+
// Don't set Icon here!
255+
// Clear out the text box so we direct the user to use the emoji picker.
256+
CurrentEmojiIcon({});
257+
}
258+
}
259+
// We're not using the VM's Icon() setter above,
260+
// so notify HasIcon changed manually
261+
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"CurrentIconType" });
262+
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"HasIcon" });
263+
}
264+
}
265+
266+
bool IconPicker::UsingNoIcon() const
267+
{
268+
return _currentIconType == IconTypes().GetAt(0);
269+
}
270+
271+
bool IconPicker::UsingBuiltInIcon() const
272+
{
273+
return _currentIconType == IconTypes().GetAt(1);
274+
}
275+
276+
bool IconPicker::UsingEmojiIcon() const
277+
{
278+
return _currentIconType == IconTypes().GetAt(2);
279+
}
280+
281+
bool IconPicker::UsingImageIcon() const
282+
{
283+
return _currentIconType == IconTypes().GetAt(3);
284+
}
285+
286+
void IconPicker::_DeduceCurrentIconType()
287+
{
288+
const auto icon = CurrentIconPath();
289+
if (icon.empty() || icon == HideIconValue)
290+
{
291+
_currentIconType = IconTypes().GetAt(0);
292+
}
293+
else if (icon.size() == 1 && (L'\uE700' <= til::at(icon, 0) && til::at(icon, 0) <= L'\uF8B3'))
294+
{
295+
_currentIconType = IconTypes().GetAt(1);
296+
_DeduceCurrentBuiltInIcon();
297+
}
298+
else if (::Microsoft::Console::Utils::IsLikelyToBeEmojiOrSymbolIcon(icon))
299+
{
300+
// We already did a range check for MDL2 Assets in the previous one,
301+
// so if we're out of that range but still short, assume we're an emoji
302+
_currentIconType = IconTypes().GetAt(2);
303+
}
304+
else
305+
{
306+
_currentIconType = IconTypes().GetAt(3);
307+
}
308+
PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"CurrentIconType" });
309+
}
310+
311+
void IconPicker::_DeduceCurrentBuiltInIcon()
312+
{
313+
const auto icon = CurrentIconPath();
314+
for (uint32_t i = 0; i < BuiltInIcons().Size(); i++)
315+
{
316+
const auto& builtIn = BuiltInIcons().GetAt(i);
317+
if (icon == unbox_value<hstring>(builtIn.EnumValue()))
318+
{
319+
CurrentBuiltInIcon(builtIn);
320+
return;
321+
}
322+
}
323+
CurrentBuiltInIcon(BuiltInIcons().GetAt(0));
324+
}
325+
326+
WUX::Controls::IconSource IconPicker::BuiltInIconConverter(const IInspectable& iconVal)
327+
{
328+
return Microsoft::Terminal::UI::IconPathConverter::IconSourceWUX(unbox_value<hstring>(iconVal));
329+
}
330+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
#pragma once
5+
6+
#include "IconPicker.g.h"
7+
#include "EnumEntry.h"
8+
#include "Utils.h"
9+
#include "cppwinrt_utils.h"
10+
11+
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
12+
{
13+
struct IconPicker : public HasScrollViewer<IconPicker>, IconPickerT<IconPicker>
14+
{
15+
public:
16+
IconPicker();
17+
18+
static Windows::UI::Xaml::Controls::IconSource BuiltInIconConverter(const Windows::Foundation::IInspectable& iconVal);
19+
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> BuiltInIcons() noexcept;
20+
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> IconTypes() noexcept;
21+
22+
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> FilteredBuiltInIconList();
23+
safe_void_coroutine Icon_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
24+
void BuiltInIconPicker_GotFocus(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
25+
void BuiltInIconPicker_TextChanged(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxTextChangedEventArgs& e);
26+
void BuiltInIconPicker_QuerySubmitted(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox& sender, const Windows::UI::Xaml::Controls::AutoSuggestBoxQuerySubmittedEventArgs& e);
27+
28+
Windows::Foundation::IInspectable CurrentIconType() const noexcept { return _currentIconType; }
29+
void CurrentIconType(const Windows::Foundation::IInspectable& value);
30+
31+
bool UsingNoIcon() const;
32+
bool UsingBuiltInIcon() const;
33+
bool UsingEmojiIcon() const;
34+
bool UsingImageIcon() const;
35+
36+
til::property_changed_event PropertyChanged;
37+
WINRT_OBSERVABLE_PROPERTY(hstring, CurrentEmojiIcon, PropertyChanged.raise);
38+
WINRT_OBSERVABLE_PROPERTY(Editor::EnumEntry, CurrentBuiltInIcon, PropertyChanged.raise, nullptr);
39+
40+
DEPENDENCY_PROPERTY(hstring, CurrentIconPath);
41+
DEPENDENCY_PROPERTY(IHostedInWindow, WindowRoot);
42+
43+
private:
44+
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _BuiltInIcons;
45+
static Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _IconTypes;
46+
Windows::Foundation::Collections::IObservableVector<Editor::EnumEntry> _filteredBuiltInIcons;
47+
std::wstring _iconFilter;
48+
Windows::Foundation::IInspectable _currentIconType{};
49+
winrt::hstring _lastIconPath;
50+
51+
static void _InitializeProperties();
52+
static void _OnCurrentIconPathChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
53+
54+
void _DeduceCurrentIconType();
55+
void _DeduceCurrentBuiltInIcon();
56+
void _updateIconFilter(std::wstring_view filter);
57+
void _updateFilteredIconList();
58+
};
59+
}
60+
61+
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
62+
{
63+
BASIC_FACTORY(IconPicker);
64+
}

0 commit comments

Comments
 (0)