feat(chat): the input row learns the window's own language
Five filled plates become two ghost buttons and a menu. The plates were ImGui defaults sitting between a drawn pill and a drawn status bar -- three shape languages in the one row a user works in, which is what the tester's screenshot made obvious. The ghosts follow the sidebar's icon buttons: nothing at rest, a held hover fill, the glyph lifting toward the accent. Symbols stay outside the menu because they are used mid-sentence. Theme, settings, screenshot and hide move in; the screenshot toggle was allowed in only because its state moved to the status bar first, as an accent pill that shows while the mode is on. A privacy state behind a closed menu answers nothing. The input field paints its own rounded surface and hands ImGui a transparent frame, so the widget draws only text and caret -- the Boutique.Inputs technique from Character Select+, no rebuild of the widget itself. Focus is a two-pixel accent rail on the left edge: the same mark the rows, the popups and the message list already use for "this is where you are". The channel pill gets the CS+ treatment, a faint white gradient and a one-pixel light along the top edge. White over the fill rather than a second hue, so it reads as depth in every palette. And the popups that open from the row are made of PopupRow now -- the sidebar row's shape, which is also exactly what CS+ draws for its own popup entries. The channel picker was the reported case: a drawn pill opening a list of naked selectables, the style breaking mid-click. A hundred-odd pixels of button reserve go back to the input field.
This commit is contained in:
@@ -389,6 +389,7 @@ internal class HellionStrings
|
||||
internal static string Settings_Preview_TitleMock => Get(nameof(Settings_Preview_TitleMock));
|
||||
internal static string Settings_Preview_StatusOpen => Get(nameof(Settings_Preview_StatusOpen));
|
||||
internal static string ChannelHeader_NotLoggedIn => Get(nameof(ChannelHeader_NotLoggedIn));
|
||||
internal static string InputBar_More_Tooltip => Get(nameof(InputBar_More_Tooltip));
|
||||
internal static string Settings_Section_Links => Get(nameof(Settings_Section_Links));
|
||||
internal static string Settings_Section_Behaviour => Get(nameof(Settings_Section_Behaviour));
|
||||
internal static string Settings_Section_Keybinds => Get(nameof(Settings_Section_Keybinds));
|
||||
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Sessió no iniciada</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Més accions</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1360,4 +1360,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Nepřihlášen</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Další akce</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1360,4 +1360,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Ikke logget ind</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Flere handlinger</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1355,4 +1355,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Nicht eingeloggt</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Weitere Aktionen</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1360,4 +1360,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Εκτός σύνδεσης</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Περισσότερες ενέργειες</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Sesión no iniciada</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Más acciones</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1360,4 +1360,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Ei kirjautuneena</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Lisää toimintoja</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Non connecté</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Plus d'actions</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1360,4 +1360,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Nincs bejelentkezve</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>További műveletek</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Non connesso</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Altre azioni</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>未ログイン</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>その他の操作</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>로그인되지 않음</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>추가 작업</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1360,4 +1360,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Ikke innlogget</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Flere handlinger</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Niet ingelogd</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Meer acties</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1360,4 +1360,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Niezalogowany</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Więcej działań</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Não conectado</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Mais ações</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1360,4 +1360,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Sem sessão iniciada</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Mais ações</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1372,4 +1372,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Not logged in</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>More actions</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Neconectat</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Mai multe acțiuni</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Вход не выполнен</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Другие действия</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Inte inloggad</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Fler åtgärder</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1360,4 +1360,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Oturum açılmadı</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Diğer işlemler</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1360,4 +1360,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>Вхід не виконано</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>Інші дії</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>未登录</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>更多操作</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1361,4 +1361,7 @@
|
||||
<data name="ChannelHeader_NotLoggedIn" xml:space="preserve">
|
||||
<value>未登入</value>
|
||||
</data>
|
||||
<data name="InputBar_More_Tooltip" xml:space="preserve">
|
||||
<value>更多操作</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -273,6 +273,18 @@ internal sealed class InputBar
|
||||
|
||||
StyleEngine.Widgets.Pill.Draw(origin, label, pillAbgr, textAbgr);
|
||||
|
||||
// The CS+ pill treatment: a faint white gradient from the top and a
|
||||
// one-pixel light along the upper edge. Reads as depth in every palette
|
||||
// because it is white over the fill, not a second hue.
|
||||
var pillDl = ImGui.GetWindowDrawList();
|
||||
var pillScale = StyleEngine.Metrics.Scale;
|
||||
pillDl.DrawVerticalGradient(origin, origin + size, 0x28FFFFFFu, 0u);
|
||||
pillDl.AddRectFilled(
|
||||
new Vector2(origin.X + 6f * pillScale, origin.Y),
|
||||
new Vector2(origin.X + size.X - 6f * pillScale, origin.Y + 1f * pillScale),
|
||||
0x50FFFFFFu
|
||||
);
|
||||
|
||||
// Hit area over the rendered pill so a click opens the channel
|
||||
// picker. InvisibleButton both reserves the layout slot and gives
|
||||
// the popup a stable anchor item.
|
||||
@@ -283,7 +295,7 @@ internal sealed class InputBar
|
||||
DrawChannelPickerPopup(tab);
|
||||
}
|
||||
|
||||
private static void DrawChannelPickerPopup(Tab? tab)
|
||||
private void DrawChannelPickerPopup(Tab? tab)
|
||||
{
|
||||
if (!ImGui.BeginPopup("##hellion-channel-picker"))
|
||||
return;
|
||||
@@ -296,14 +308,27 @@ internal sealed class InputBar
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.Dummy(new Vector2(150f * StyleEngine.Metrics.Scale, 0f));
|
||||
|
||||
var i = 0;
|
||||
foreach (var chatType in tab.SelectedChannels.Keys)
|
||||
{
|
||||
if (chatType.ToInputChannel() is not { } input)
|
||||
continue;
|
||||
|
||||
var isCurrent = tab.CurrentChannel.Channel == input;
|
||||
if (ImGui.Selectable(input.ToChatType().Name(), isCurrent))
|
||||
if (
|
||||
StyleEngine.Widgets.PopupRow.Draw(
|
||||
$"##ch-{i++}",
|
||||
input.ToChatType().Name(),
|
||||
isCurrent,
|
||||
_fonts
|
||||
)
|
||||
)
|
||||
{
|
||||
tab.CurrentChannel.SetChannel(input);
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -325,6 +350,33 @@ internal sealed class InputBar
|
||||
Activate = false;
|
||||
}
|
||||
|
||||
// Boutique.Inputs technique: paint the surface, then hand ImGui a
|
||||
// transparent frame so the widget draws only text and caret. The focus
|
||||
// rail is the same two-pixel accent bar the rows and popups carry --
|
||||
// one vocabulary for "this is where you are".
|
||||
var scale = StyleEngine.Metrics.Scale;
|
||||
var fieldOrigin = ImGui.GetCursorScreenPos();
|
||||
var fieldSize = new Vector2(
|
||||
ImGui.GetContentRegionAvail().X - QuickButtonsReserve,
|
||||
ImGui.GetFrameHeight()
|
||||
);
|
||||
ImGui
|
||||
.GetWindowDrawList()
|
||||
.AddRectFilled(
|
||||
fieldOrigin,
|
||||
fieldOrigin + fieldSize,
|
||||
ColourUtil.RgbaToAbgr(_themes.Active.Colors.FrameBg),
|
||||
6f * scale
|
||||
);
|
||||
|
||||
using var frameBg = ImRaii.PushColor(ImGuiCol.FrameBg, Vector4.Zero);
|
||||
using var frameBgHovered = ImRaii.PushColor(ImGuiCol.FrameBgHovered, Vector4.Zero);
|
||||
using var frameBgActive = ImRaii.PushColor(ImGuiCol.FrameBgActive, Vector4.Zero);
|
||||
using var framePad = ImRaii.PushStyle(
|
||||
ImGuiStyleVar.FramePadding,
|
||||
new Vector2(10f * scale, ImGui.GetStyle().FramePadding.Y)
|
||||
);
|
||||
|
||||
ImGui.SetNextItemWidth(-QuickButtonsReserve);
|
||||
if (
|
||||
ImGui.InputText(
|
||||
@@ -343,6 +395,15 @@ internal sealed class InputBar
|
||||
_commandHelpWindow.IsOpen = false;
|
||||
TrySend(activeTab);
|
||||
}
|
||||
|
||||
if (ImGui.IsItemActive())
|
||||
ImGui
|
||||
.GetWindowDrawList()
|
||||
.AddRectFilled(
|
||||
fieldOrigin,
|
||||
new Vector2(fieldOrigin.X + 2f * scale, fieldOrigin.Y + fieldSize.Y),
|
||||
ColourUtil.RgbaToAbgr(_themes.Active.Colors.Accent)
|
||||
);
|
||||
DrawInputContextMenu();
|
||||
|
||||
_isFocused = ImGui.IsItemFocused();
|
||||
@@ -627,106 +688,175 @@ internal sealed class InputBar
|
||||
return (current == InputChannel.Invalid ? text : $"{current.Prefix()} {text}", false);
|
||||
}
|
||||
|
||||
// Two ghost buttons and a menu, where five filled plates used to sit. The
|
||||
// plates were ImGui defaults in a row whose pill and status bar are drawn;
|
||||
// the ghosts follow the sidebar's icon buttons instead -- nothing at rest, a
|
||||
// held hover fill, the glyph lifting toward the accent.
|
||||
//
|
||||
// Symbols stay outside the menu because they are used mid-sentence; a click
|
||||
// through a drawer for that would be a regression. The screenshot toggle may
|
||||
// live in the menu only because its state moved to the status bar first --
|
||||
// the whole point of that mode is knowing what your screen shows BEFORE the
|
||||
// screenshot key, and a state behind a closed menu answers nothing.
|
||||
private void DrawQuickButtons()
|
||||
{
|
||||
// Collected here and drawn after the icon font is popped. Inside the push
|
||||
// it rendered against the FontAwesome atlas, which has no ASCII glyphs, so
|
||||
// every tooltip came out as an empty box.
|
||||
string? tooltip = null;
|
||||
|
||||
using (_fonts.FontAwesome.Push())
|
||||
{
|
||||
if (ImGui.Button(FontAwesomeIcon.SmileBeam.ToIconString()))
|
||||
_symbolPicker.OpenPopup();
|
||||
if (ImGui.IsItemHovered())
|
||||
tooltip = HellionStrings.InputBar_InsertSymbol_Tooltip;
|
||||
if (DrawGhostButton("##qb-symbols", FontAwesomeIcon.SmileBeam))
|
||||
_symbolPicker.OpenPopup();
|
||||
if (ImGui.IsItemHovered())
|
||||
tooltip = HellionStrings.InputBar_InsertSymbol_Tooltip;
|
||||
|
||||
if (_themeQuickPicker is not null)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(FontAwesomeIcon.Palette.ToIconString()))
|
||||
_themeQuickPicker.OpenPopup();
|
||||
if (ImGui.IsItemHovered())
|
||||
tooltip = HellionStrings.Settings_QuickPicker_Tooltip;
|
||||
}
|
||||
ImGui.SameLine(0f, 4f * StyleEngine.Metrics.Scale);
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(FontAwesomeIcon.Cog.ToIconString()))
|
||||
{
|
||||
_onOpenSettings();
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
tooltip = HellionStrings.InputBar_Settings_Tooltip;
|
||||
if (DrawGhostButton("##qb-more", FontAwesomeIcon.EllipsisH))
|
||||
ImGui.OpenPopup("##hellion-more-menu");
|
||||
if (ImGui.IsItemHovered())
|
||||
tooltip = HellionStrings.InputBar_More_Tooltip;
|
||||
|
||||
// Screenshot mode. It lived only in the right-click menu on a player
|
||||
// name, which for a privacy feature is the same as not existing --
|
||||
// reported as missing by a tester who had been using the plugin for
|
||||
// months. Lit when active, because a mode you cannot see the state of
|
||||
// is worse than no mode.
|
||||
ImGui.SameLine();
|
||||
var shooting = Plugin.Config.ScreenshotMode;
|
||||
using (
|
||||
ImRaii.PushColor(
|
||||
ImGuiCol.Text,
|
||||
ColourUtil.RgbaToVector4(
|
||||
_resolver.Resolve(Token.AccentEmber, _themes.Active.Colors)
|
||||
),
|
||||
shooting
|
||||
)
|
||||
)
|
||||
{
|
||||
if (ImGui.Button(FontAwesomeIcon.Camera.ToIconString()))
|
||||
Plugin.Config.ScreenshotMode = !Plugin.Config.ScreenshotMode;
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
tooltip = Language.Context_ScreenshotMode;
|
||||
|
||||
// Hides the window (1.5.6 UserHide). One-way — Enter brings it back.
|
||||
// Main window only; last in the row there.
|
||||
if (Plugin.Config.ShowHideButton && _onHideWindow is not null)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(FontAwesomeIcon.EyeSlash.ToIconString()))
|
||||
_onHideWindow();
|
||||
if (ImGui.IsItemHovered())
|
||||
tooltip = HellionStrings.InputBar_HideChat_Tooltip;
|
||||
}
|
||||
|
||||
// Pop-in, in the pop-out windows only. It sits here rather than in a
|
||||
// header row because a pop-out with its title bar on had no header at
|
||||
// all -- and the title bar carries no close button, since closing has
|
||||
// to go through the pool to release the slot.
|
||||
if (OnPopIn is not null)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
// Red, and contrast-checked against the button plate it sits on
|
||||
// rather than taken raw: several themes ship a danger colour that
|
||||
// is nearly invisible on their own button fill.
|
||||
var danger = ColourUtil.RgbaToAbgr(
|
||||
_resolver.Resolve(Token.StatusDanger, _themes.Active.Colors)
|
||||
);
|
||||
var plate = ColourUtil.Vector4ToAbgr(ImGui.GetStyle().Colors[(int)ImGuiCol.Button]);
|
||||
using (
|
||||
ImRaii.PushColor(
|
||||
ImGuiCol.Text,
|
||||
ColourUtil.RgbaToVector4(
|
||||
ColourUtil.RgbaToAbgr(ColourUtil.EnsureContrast(danger, plate, 3f))
|
||||
)
|
||||
)
|
||||
)
|
||||
{
|
||||
if (ImGui.Button(FontAwesomeIcon.Times.ToIconString()))
|
||||
OnPopIn();
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
tooltip = HellionStrings.InputBar_PopIn_Tooltip;
|
||||
}
|
||||
}
|
||||
DrawMoreMenu();
|
||||
|
||||
if (tooltip is not null)
|
||||
ImGui.SetTooltip(tooltip);
|
||||
}
|
||||
|
||||
// Sidebar language: no plate at rest, held hover fill, glyph toward accent.
|
||||
private bool DrawGhostButton(string id, FontAwesomeIcon icon, bool lit = false)
|
||||
{
|
||||
var scale = StyleEngine.Metrics.Scale;
|
||||
var side = MathF.Round(24f * scale);
|
||||
var origin = ImGui.GetCursorScreenPos();
|
||||
var max = origin + new Vector2(side, side);
|
||||
|
||||
var clicked = ImGui.InvisibleButton(id, new Vector2(side, side));
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
var amount = StyleEngine.HoverState.Query(ImGui.GetID(id), hovered);
|
||||
|
||||
var c = _themes.Active.Colors;
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
|
||||
if (amount > 0f)
|
||||
dl.AddRectFilled(
|
||||
origin,
|
||||
max,
|
||||
ColourUtil.ApplyAlpha(ColourUtil.RgbaToAbgr(c.SurfaceHover), amount),
|
||||
3f * scale
|
||||
);
|
||||
|
||||
var accent = ColourUtil.RgbaToAbgr(c.Accent);
|
||||
var tint = ColourUtil.RgbaToAbgr(ColourUtil.EnsureContrast(c.TextMuted, c.ChildBg, 4.5f));
|
||||
if (lit)
|
||||
tint = accent;
|
||||
else if (amount > 0f)
|
||||
tint = ColourUtil.Lerp(tint, accent, amount * 0.6f);
|
||||
|
||||
using (_fonts.FontAwesome.Push())
|
||||
{
|
||||
var glyph = icon.ToIconString();
|
||||
var size = ImGui.CalcTextSize(glyph);
|
||||
dl.AddText(origin + (max - origin - size) * 0.5f, tint, glyph);
|
||||
}
|
||||
|
||||
return clicked;
|
||||
}
|
||||
|
||||
private void DrawMoreMenu()
|
||||
{
|
||||
if (!ImGui.BeginPopup("##hellion-more-menu"))
|
||||
return;
|
||||
|
||||
// Actions run after EndPopup: the theme picker opens its own popup at
|
||||
// this component's ID scope, not inside the menu's.
|
||||
var openTheme = false;
|
||||
|
||||
try
|
||||
{
|
||||
ImGui.Dummy(new Vector2(160f * StyleEngine.Metrics.Scale, 0f));
|
||||
|
||||
if (
|
||||
_themeQuickPicker is not null
|
||||
&& StyleEngine.Widgets.PopupRow.Draw(
|
||||
"##mm-theme",
|
||||
HellionStrings.Settings_QuickPicker_Tooltip,
|
||||
active: false,
|
||||
_fonts,
|
||||
FontAwesomeIcon.Palette
|
||||
)
|
||||
)
|
||||
{
|
||||
openTheme = true;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
if (
|
||||
StyleEngine.Widgets.PopupRow.Draw(
|
||||
"##mm-settings",
|
||||
HellionStrings.InputBar_Settings_Tooltip,
|
||||
active: false,
|
||||
_fonts,
|
||||
FontAwesomeIcon.Cog
|
||||
)
|
||||
)
|
||||
{
|
||||
_onOpenSettings();
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
// Shows its state right here as the active row, and again in the
|
||||
// status bar once on -- the menu closes, the awareness must not.
|
||||
if (
|
||||
StyleEngine.Widgets.PopupRow.Draw(
|
||||
"##mm-screenshot",
|
||||
Language.Context_ScreenshotMode,
|
||||
active: Plugin.Config.ScreenshotMode,
|
||||
_fonts,
|
||||
FontAwesomeIcon.Camera
|
||||
)
|
||||
)
|
||||
{
|
||||
Plugin.Config.ScreenshotMode = !Plugin.Config.ScreenshotMode;
|
||||
}
|
||||
|
||||
if (
|
||||
Plugin.Config.ShowHideButton
|
||||
&& _onHideWindow is not null
|
||||
&& StyleEngine.Widgets.PopupRow.Draw(
|
||||
"##mm-hide",
|
||||
HellionStrings.InputBar_HideChat_Tooltip,
|
||||
active: false,
|
||||
_fonts,
|
||||
FontAwesomeIcon.EyeSlash
|
||||
)
|
||||
)
|
||||
{
|
||||
_onHideWindow();
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
if (
|
||||
OnPopIn is not null
|
||||
&& StyleEngine.Widgets.PopupRow.Draw(
|
||||
"##mm-popin",
|
||||
HellionStrings.InputBar_PopIn_Tooltip,
|
||||
active: false,
|
||||
_fonts,
|
||||
FontAwesomeIcon.Times
|
||||
)
|
||||
)
|
||||
{
|
||||
OnPopIn();
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
if (openTheme)
|
||||
_themeQuickPicker!.OpenPopup();
|
||||
}
|
||||
|
||||
// Test-only hook; do not call from production code.
|
||||
internal void TestSetPendingMessageForSelfTest(string value) => _pendingMessage = value;
|
||||
|
||||
|
||||
@@ -203,6 +203,30 @@ internal sealed class StatusBar
|
||||
FontAwesomeIcon.Lock,
|
||||
_fonts.FontAwesome
|
||||
);
|
||||
// Screenshot mode announces itself here while it is on. The toggle sits
|
||||
// in the input row's menu, and a closed menu answers nothing -- the whole
|
||||
// point of the mode is knowing what the screen shows before the
|
||||
// screenshot key is pressed. Accent text, because this one is a state
|
||||
// the user chose, not ambient information.
|
||||
if (Plugin.Config.ScreenshotMode)
|
||||
{
|
||||
var camWidth = StyleEngine.Widgets.Pill.MeasureIcon(
|
||||
FontAwesomeIcon.Camera,
|
||||
_fonts.FontAwesome
|
||||
);
|
||||
var camLabel = Language.Context_ScreenshotMode;
|
||||
if (Fits(camLabel, withDot: false, camWidth))
|
||||
x +=
|
||||
DrawSlot(
|
||||
new Vector2(x, top),
|
||||
camLabel,
|
||||
pillFill,
|
||||
palette.Abgr(Token.AccentPrimary, colors),
|
||||
null,
|
||||
(FontAwesomeIcon.Camera, _fonts.FontAwesome)
|
||||
) + gap;
|
||||
}
|
||||
|
||||
if (Fits(privacyLabel, withDot: false, lockWidth))
|
||||
x +=
|
||||
DrawSlot(
|
||||
|
||||
@@ -27,10 +27,10 @@ internal static class Metrics
|
||||
// --- Input bar ---
|
||||
internal const float InputBarHeightRaw = 32f;
|
||||
|
||||
// Raised from 130 in v1.13.0 for the screenshot-mode button. Five buttons in
|
||||
// the main window (symbols, theme, screenshot, settings, hide) and five in a
|
||||
// pop-out, where pop-in takes the place of hide.
|
||||
internal const float InputQuickButtonsReserveRaw = 156f;
|
||||
// Two ghost buttons since v1.14.0 -- symbols and the more-menu. Everything
|
||||
// else moved into the menu, and the hundred-odd pixels went back to the
|
||||
// input field.
|
||||
internal const float InputQuickButtonsReserveRaw = 62f;
|
||||
|
||||
// --- Honorific header ---
|
||||
internal const float HonorificHeightRaw = 30f;
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.Ui.StyleEngine.Widgets;
|
||||
|
||||
// The row a popup is made of. Replaces ImGui.Selectable inside the plugin's
|
||||
// popups, which carried the right colours through GlobalStyleScope but none of
|
||||
// the shape the rest of the window speaks: no held hover, no accent bar on the
|
||||
// active entry.
|
||||
//
|
||||
// The shape is the sidebar row's, and it is also what Character Select+ draws
|
||||
// for its popup entries (Boutique.Dropdowns, DrawPopupRow): a soft fill on
|
||||
// hover, a two-pixel accent bar when active. One conversation, wherever a list
|
||||
// appears.
|
||||
internal static class PopupRow
|
||||
{
|
||||
private const float PadXRaw = 10f;
|
||||
private const float PadYRaw = 5f;
|
||||
private const float IconGapRaw = 8f;
|
||||
private const float AccentBarRaw = 2f;
|
||||
|
||||
internal static bool Draw(
|
||||
string id,
|
||||
string label,
|
||||
bool active,
|
||||
FontManager fonts,
|
||||
FontAwesomeIcon? icon = null
|
||||
)
|
||||
{
|
||||
var scale = Metrics.Scale;
|
||||
var theme = Plugin.Instance.ThemeRegistry.Active;
|
||||
var c = theme.Colors;
|
||||
|
||||
var height = ImGui.GetTextLineHeight() + MathF.Round(PadYRaw * 2f * scale);
|
||||
var width = ImGui.GetContentRegionAvail().X;
|
||||
var origin = ImGui.GetCursorScreenPos();
|
||||
var max = origin + new Vector2(width, height);
|
||||
|
||||
var clicked = ImGui.InvisibleButton(id, new Vector2(width, height));
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
var amount = HoverState.Query(ImGui.GetID(id), hovered);
|
||||
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
var accentAbgr = ColourUtil.RgbaToAbgr(c.Accent);
|
||||
|
||||
// Active first, hover on top -- an active entry still answers the mouse.
|
||||
if (active)
|
||||
dl.AddRectFilled(origin, max, ColourUtil.ApplyAlpha(accentAbgr, 0.10f), 3f * scale);
|
||||
|
||||
if (amount > 0f)
|
||||
dl.AddRectFilled(
|
||||
origin,
|
||||
max,
|
||||
ColourUtil.ApplyAlpha(ColourUtil.RgbaToAbgr(c.SurfaceHover), amount),
|
||||
3f * scale
|
||||
);
|
||||
|
||||
if (active)
|
||||
dl.AddRectFilled(
|
||||
origin,
|
||||
new Vector2(origin.X + AccentBarRaw * scale, max.Y),
|
||||
accentAbgr
|
||||
);
|
||||
|
||||
var textAbgr = ColourUtil.RgbaToAbgr(
|
||||
ColourUtil.EnsureContrast(c.TextPrimary, c.ChildBg, 4.5f)
|
||||
);
|
||||
if (active || amount > 0f)
|
||||
textAbgr = ColourUtil.Lerp(textAbgr, accentAbgr, active ? 0.6f : amount * 0.4f);
|
||||
|
||||
var x = origin.X + PadXRaw * scale;
|
||||
|
||||
if (icon is { } glyph)
|
||||
{
|
||||
using (fonts.FontAwesome.Push())
|
||||
{
|
||||
var s = glyph.ToIconString();
|
||||
var size = ImGui.CalcTextSize(s);
|
||||
dl.AddText(new Vector2(x, origin.Y + (height - size.Y) * 0.5f), textAbgr, s);
|
||||
x += size.X + IconGapRaw * scale;
|
||||
}
|
||||
}
|
||||
|
||||
dl.AddText(
|
||||
new Vector2(x, origin.Y + (height - ImGui.GetTextLineHeight()) * 0.5f),
|
||||
textAbgr,
|
||||
label
|
||||
);
|
||||
|
||||
return clicked;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user