Compare commits

...

6 Commits

Author SHA1 Message Date
b3e545e0bb
xP: bump copyright years
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
2024-11-14 16:27:56 +01:00
cd76702ab2
xA/xW: dehighlight current buffer appropriately 2024-11-14 16:14:54 +01:00
977b073b58
xA: enforce internal icon from the start 2024-11-14 16:14:49 +01:00
46be4836df
xW: print the separator line at the end of buffer 2024-11-14 13:50:51 +01:00
05a41b2629
xA/xM/xW: refresh renamed buffers correctly
Rendering takes the current buffer into account,
so change its value before using it, not afterwards.

The order happened to not matter on at least Windows,
because we just queue a message.
2024-11-14 11:41:09 +01:00
a62ed5bbac
xA/xM: refresh buffer list on dehighlight 2024-11-14 11:41:08 +01:00
4 changed files with 191 additions and 147 deletions

152
xA/xA.go
View File

@ -363,61 +363,6 @@ func bufferByName(name string) *buffer {
return nil
}
func bufferActivate(name string) {
relaySend(RelayCommandData{
Variant: &RelayCommandDataBufferActivate{BufferName: name},
}, nil)
}
func bufferToggleUnimportant(name string) {
relaySend(RelayCommandData{
Variant: &RelayCommandDataBufferToggleUnimportant{BufferName: name},
}, nil)
}
func bufferPushLine(b *buffer, line bufferLine) {
b.lines = append(b.lines, line)
// Fyne's text layouting is extremely slow.
// The limit could be made configurable,
// and we could use a ring buffer approach to storing the lines.
if len(b.lines) > 100 {
b.lines = slices.Delete(b.lines, 0, 1)
}
}
// --- Current buffer ----------------------------------------------------------
func bufferToggleLogFinish(err string, response *RelayResponseDataBufferLog) {
if response == nil {
showErrorMessage(err)
return
}
wLog.SetText(string(response.Log))
wLog.Show()
wRichScroll.Hide()
}
func bufferToggleLog() {
if wLog.Visible() {
wRichScroll.Show()
wLog.Hide()
wLog.SetText("")
return
}
name := bufferCurrent
relaySend(RelayCommandData{Variant: &RelayCommandDataBufferLog{
BufferName: name,
}}, func(err string, response *RelayResponseData) {
if bufferCurrent == name {
bufferToggleLogFinish(
err, response.Variant.(*RelayResponseDataBufferLog))
}
})
}
func bufferAtBottom() bool {
return wRichScroll.Offset.Y >=
wRichScroll.Content.Size().Height-wRichScroll.Size().Height
@ -432,22 +377,31 @@ func bufferScrollToBottom() {
refreshStatus()
}
func bufferPushLine(b *buffer, line bufferLine) {
b.lines = append(b.lines, line)
// Fyne's text layouting is extremely slow.
// The limit could be made configurable,
// and we could use a ring buffer approach to storing the lines.
if len(b.lines) > 100 {
b.lines = slices.Delete(b.lines, 0, 1)
}
}
// --- UI state refresh --------------------------------------------------------
func refreshIcon() {
highlighted := false
resource := resourceIconNormal
for _, b := range buffers {
if b.highlighted {
highlighted = true
resource = resourceIconHighlighted
break
}
}
if highlighted {
wWindow.SetIcon(resourceIconHighlighted)
} else {
wWindow.SetIcon(resourceIconNormal)
}
// Prevent deadlocks (though it might have a race condition).
// https://github.com/fyne-io/fyne/issues/5266
go func() { wWindow.SetIcon(resource) }()
}
func refreshTopic(topic []bufferLineItem) {
@ -515,6 +469,63 @@ func refreshStatus() {
wStatus.SetText(status)
}
func recheckHighlighted() {
// Corresponds to the logic toggling the bool on.
if b := bufferByName(bufferCurrent); b != nil &&
b.highlighted && bufferAtBottom() &&
inForeground && !wLog.Visible() {
b.highlighted = false
refreshIcon()
refreshBufferList()
}
}
// --- Buffer actions ----------------------------------------------------------
func bufferActivate(name string) {
relaySend(RelayCommandData{
Variant: &RelayCommandDataBufferActivate{BufferName: name},
}, nil)
}
func bufferToggleUnimportant(name string) {
relaySend(RelayCommandData{
Variant: &RelayCommandDataBufferToggleUnimportant{BufferName: name},
}, nil)
}
func bufferToggleLogFinish(err string, response *RelayResponseDataBufferLog) {
if response == nil {
showErrorMessage(err)
return
}
wLog.SetText(string(response.Log))
wLog.Show()
wRichScroll.Hide()
}
func bufferToggleLog() {
if wLog.Visible() {
wRichScroll.Show()
wLog.Hide()
wLog.SetText("")
recheckHighlighted()
return
}
name := bufferCurrent
relaySend(RelayCommandData{Variant: &RelayCommandDataBufferLog{
BufferName: name,
}}, func(err string, response *RelayResponseData) {
if bufferCurrent == name {
bufferToggleLogFinish(
err, response.Variant.(*RelayResponseDataBufferLog))
}
})
}
// --- RichText formatting -----------------------------------------------------
func defaultBufferLineItem() bufferLineItem { return bufferLineItem{} }
@ -756,6 +767,7 @@ func refreshBuffer(b *buffer) {
bufferPrintAndWatchTrailingDateChanges()
wRichText.Refresh()
bufferScrollToBottom()
recheckHighlighted()
}
// --- Event processing --------------------------------------------------------
@ -921,11 +933,11 @@ func relayProcessMessage(m *RelayEventMessage) {
b.bufferName = data.New
refreshBufferList()
if data.BufferName == bufferCurrent {
bufferCurrent = data.New
refreshStatus()
}
refreshBufferList()
if data.BufferName == bufferLast {
bufferLast = data.New
}
@ -1374,6 +1386,9 @@ func (l *customLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
}
if toBottom {
bufferScrollToBottom()
} else {
recheckHighlighted()
refreshStatus()
}
}
@ -1490,16 +1505,14 @@ func main() {
a := app.New()
a.Settings().SetTheme(&customTheme{})
a.SetIcon(resourceIconNormal)
wWindow = a.NewWindow(projectName)
wWindow.Resize(fyne.NewSize(640, 480))
a.Lifecycle().SetOnEnteredForeground(func() {
// TODO(p): Does this need locking?
inForeground = true
if b := bufferByName(bufferCurrent); b != nil {
b.highlighted = false
refreshIcon()
}
recheckHighlighted()
})
a.Lifecycle().SetOnExitedForeground(func() {
inForeground = false
@ -1540,7 +1553,10 @@ func main() {
wRichText = widget.NewRichText()
wRichText.Wrapping = fyne.TextWrapWord
wRichScroll = container.NewVScroll(wRichText)
wRichScroll.OnScrolled = func(position fyne.Position) { refreshStatus() }
wRichScroll.OnScrolled = func(position fyne.Position) {
recheckHighlighted()
refreshStatus()
}
wLog = newLogEntry()
wLog.Wrapping = fyne.TextWrapWord
wLog.Hide()

View File

@ -842,11 +842,11 @@ relayRPC.onEvent = { message in
b.bufferName = data.new
refreshBufferList()
if b.bufferName == relayBufferCurrent {
relayBufferCurrent = data.new
refreshStatus()
}
refreshBufferList()
if b.bufferName == relayBufferLast {
relayBufferLast = data.new
}
@ -1203,6 +1203,7 @@ class WindowDelegate: NSObject, NSWindowDelegate {
b.highlighted = false
refreshIcon()
refreshBufferList()
}
// Buffer indexes rotated to start after the current buffer.

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 - 2023, Přemysl Eric Janouch <p@janouch.name>
// Copyright (c) 2022 - 2024, Přemysl Eric Janouch <p@janouch.name>
// SPDX-License-Identifier: 0BSD
import * as Relay from './proto.js'

181
xW/xW.cpp
View File

@ -1,7 +1,7 @@
/*
* xW.cpp: Win32 frontend for xC
*
* Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
* Copyright (c) 2023 - 2024, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@ -255,73 +255,6 @@ buffer_by_name(const std::wstring &name)
return nullptr;
}
static void
buffer_activate(const std::wstring &name)
{
auto activate = new Relay::CommandData_BufferActivate();
activate->buffer_name = name;
relay_send(activate);
}
static void
buffer_toggle_unimportant(const std::wstring &name)
{
auto toggle = new Relay::CommandData_BufferToggleUnimportant();
toggle->buffer_name = name;
relay_send(toggle);
}
// --- Current buffer ----------------------------------------------------------
static void
buffer_toggle_log(
const std::wstring &error, const Relay::ResponseData_BufferLog *response)
{
if (!response) {
show_error_message(error.c_str());
return;
}
std::wstring log;
if (!LibertyXDR::utf8_to_wstring(
response->log.data(), response->log.size(), log)) {
show_error_message(L"Invalid encoding.");
return;
}
std::wstring filtered;
for (auto wch : log) {
if (wch == L'\n')
filtered += L"\r\n";
else
filtered += wch;
}
SetWindowText(g.hwndBufferLog, filtered.c_str());
ShowWindow(g.hwndBuffer, SW_HIDE);
ShowWindow(g.hwndBufferLog, SW_SHOW);
}
static void
buffer_toggle_log()
{
if (IsWindowVisible(g.hwndBufferLog)) {
ShowWindow(g.hwndBufferLog, SW_HIDE);
ShowWindow(g.hwndBuffer, SW_SHOW);
SetWindowText(g.hwndBufferLog, L"");
return;
}
auto log = new Relay::CommandData_BufferLog();
log->buffer_name = g.buffer_current;
relay_send(log, [name = g.buffer_current](auto error, auto response) {
if (g.buffer_current != name)
return;
buffer_toggle_log(error,
dynamic_cast<const Relay::ResponseData_BufferLog *>(response));
});
}
static bool
buffer_at_bottom()
{
@ -354,6 +287,7 @@ refresh_icon()
if (b.highlighted)
icon = g.hiconHighlighted;
// XXX: This may not change the taskbar icon.
SendMessage(g.hwndMain, WM_SETICON, ICON_SMALL, (LPARAM) icon);
SendMessage(g.hwndMain, WM_SETICON, ICON_BIG, (LPARAM) icon);
}
@ -430,6 +364,88 @@ refresh_status()
SetWindowText(g.hwndStatus, status.c_str());
}
static void
recheck_highlighted()
{
// Corresponds to the logic toggling the bool on.
auto b = buffer_by_name(g.buffer_current);
if (b && b->highlighted && buffer_at_bottom() &&
!IsIconic(g.hwndMain) && !IsWindowVisible(g.hwndBufferLog)) {
b->highlighted = false;
refresh_icon();
refresh_buffer_list();
}
}
// --- Buffer actions ----------------------------------------------------------
static void
buffer_activate(const std::wstring &name)
{
auto activate = new Relay::CommandData_BufferActivate();
activate->buffer_name = name;
relay_send(activate);
}
static void
buffer_toggle_unimportant(const std::wstring &name)
{
auto toggle = new Relay::CommandData_BufferToggleUnimportant();
toggle->buffer_name = name;
relay_send(toggle);
}
static void
buffer_toggle_log(
const std::wstring &error, const Relay::ResponseData_BufferLog *response)
{
if (!response) {
show_error_message(error.c_str());
return;
}
std::wstring log;
if (!LibertyXDR::utf8_to_wstring(
response->log.data(), response->log.size(), log)) {
show_error_message(L"Invalid encoding.");
return;
}
std::wstring filtered;
for (auto wch : log) {
if (wch == L'\n')
filtered += L"\r\n";
else
filtered += wch;
}
SetWindowText(g.hwndBufferLog, filtered.c_str());
ShowWindow(g.hwndBuffer, SW_HIDE);
ShowWindow(g.hwndBufferLog, SW_SHOW);
}
static void
buffer_toggle_log()
{
if (IsWindowVisible(g.hwndBufferLog)) {
ShowWindow(g.hwndBufferLog, SW_HIDE);
ShowWindow(g.hwndBuffer, SW_SHOW);
SetWindowText(g.hwndBufferLog, L"");
recheck_highlighted();
return;
}
auto log = new Relay::CommandData_BufferLog();
log->buffer_name = g.buffer_current;
relay_send(log, [name = g.buffer_current](auto error, auto response) {
if (g.buffer_current != name)
return;
buffer_toggle_log(error,
dynamic_cast<const Relay::ResponseData_BufferLog *>(response));
});
}
// --- Rich Edit formatting ----------------------------------------------------
static COLORREF
@ -695,7 +711,7 @@ buffer_print_line(std::vector<BufferLine>::const_iterator begin,
static void
buffer_print_separator()
{
bool sameline = !GetWindowTextLength(g.hwndBuffer);
bool sameline = !buffer_reset_selection();
CHARFORMAT2 format = default_charformat();
format.dwEffects &= ~CFE_AUTOCOLOR;
@ -728,6 +744,7 @@ refresh_buffer(const Buffer &b)
buffer_print_and_watch_trailing_date_changes();
buffer_scroll_to_bottom();
// We will get a scroll event, so no need to recheck_highlighted() here.
SendMessage(g.hwndBuffer, WM_SETREDRAW, (WPARAM) TRUE, 0);
InvalidateRect(g.hwndBuffer, NULL, TRUE);
@ -749,8 +766,9 @@ relay_process_buffer_line(Buffer &b, Relay::EventData_BufferLine &m)
// Retained mode is complicated.
bool display = (!m.is_unimportant || !bc->hide_unimportant) &&
(b.buffer_name == g.buffer_current || m.leak_to_active);
// XXX: It would be great if it didn't autoscroll when focused.
bool to_bottom = display &&
buffer_at_bottom();
(buffer_at_bottom() || GetFocus() == g.hwndBuffer);
bool visible = display &&
to_bottom &&
!IsIconic(g.hwndMain) &&
@ -914,11 +932,11 @@ relay_process_message(const Relay::EventMessage &m)
b->buffer_name = data.new_;
refresh_buffer_list();
if (data.buffer_name == g.buffer_current) {
g.buffer_current = data.new_;
refresh_status();
}
refresh_buffer_list();
if (data.buffer_name == g.buffer_last)
g.buffer_last = data.new_;
break;
@ -1465,6 +1483,7 @@ richedit_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
{
// Dragging the scrollbar doesn't result in EN_VSCROLL.
LRESULT lResult = DefSubclassProc(hWnd, uMsg, wParam, lParam);
recheck_highlighted();
refresh_status();
return lResult;
}
@ -1522,8 +1541,12 @@ process_resize(UINT w, UINT h)
MoveWindow(g.hwndBufferList, 3, top, 150, h - top - bottom, FALSE);
MoveWindow(g.hwndBuffer, 156, top, w - 159, h - top - bottom, FALSE);
MoveWindow(g.hwndBufferLog, 156, top, w - 159, h - top - bottom, FALSE);
if (to_bottom)
if (to_bottom) {
buffer_scroll_to_bottom();
} else {
recheck_highlighted();
refresh_status();
}
InvalidateRect(g.hwndMain, NULL, TRUE);
}
@ -1685,8 +1708,10 @@ window_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
}
case WM_SYSCOMMAND:
{
// We're not deiconified yet, so duplicate recheck_highlighted().
auto b = buffer_by_name(g.buffer_current);
if (b && wParam == SC_RESTORE) {
if (wParam == SC_RESTORE && b && b->highlighted && buffer_at_bottom() &&
!IsWindowVisible(g.hwndBufferLog)) {
b->highlighted = false;
refresh_icon();
}
@ -1694,13 +1719,15 @@ window_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
break;
}
case WM_COMMAND:
if (!lParam)
if (!lParam) {
process_accelerator(LOWORD(wParam));
else if (lParam == (LPARAM) g.hwndBufferList)
} else if (lParam == (LPARAM) g.hwndBufferList) {
process_bufferlist_notification(HIWORD(wParam));
else if (lParam == (LPARAM) g.hwndBuffer &&
HIWORD(wParam) == EN_VSCROLL)
} else if (lParam == (LPARAM) g.hwndBuffer &&
HIWORD(wParam) == EN_VSCROLL) {
recheck_highlighted();
refresh_status();
}
return 0;
case WM_NOTIFY:
switch (((LPNMHDR) lParam)->code) {