From cdff879900779477a6b9ed020fb390b2484bd525 Mon Sep 17 00:00:00 2001
From: Přemysl Eric Janouch
Date: Mon, 23 Feb 2026 23:54:33 +0100
Subject: WIP: AppKit
---
CMakeLists.txt | 12 ++
LICENSE | 2 +-
README.adoc | 8 +-
config.h.in | 1 +
nncmpp.c | 426 ++++++++++++++++++++++++++++++++++++++++++++++-----------
5 files changed, 362 insertions(+), 87 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 93df5e8..7e0f6c1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -68,6 +68,15 @@ if (WITH_PULSE)
list (APPEND extra_libraries ${libpulse_LIBRARIES})
endif ()
+add_option (WITH_APPKIT "Build with AppKit support" "${APPLE}")
+if (WITH_APPKIT)
+ enable_language (OBJC)
+ set (CMAKE_OBJC_FLAGS
+ "${CMAKE_OBJC_FLAGS} -std=gnu99 -Wall -Wextra -Wno-unused-function")
+ list (APPEND extra_libraries
+ "-framework AppKit" "-framework CoreFoundation")
+endif ()
+
pkg_check_modules (x11 x11 xrender xft fontconfig libpng)
add_option (WITH_X11 "Build with X11 support" "${x11_FOUND}")
if (WITH_X11)
@@ -120,6 +129,9 @@ add_custom_command (OUTPUT ${actions}
# Build the main executable and link it
add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c ${actions})
+if (WITH_APPKIT)
+ set_source_files_properties (${PROJECT_NAME}.c PROPERTIES LANGUAGE OBJC)
+endif ()
target_link_libraries (${PROJECT_NAME} ${Unistring_LIBRARIES}
${Ncursesw_LIBRARIES} ${Termo_LIBRARIES} ${curl_LIBRARIES}
${extra_libraries})
diff --git a/LICENSE b/LICENSE
index d5d26bc..f44220d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2016 - 2024, Přemysl Eric Janouch
+Copyright (c) 2016 - 2026, Přemysl Eric Janouch
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
diff --git a/README.adoc b/README.adoc
index 775c5a3..4fdebbe 100644
--- a/README.adoc
+++ b/README.adoc
@@ -2,8 +2,8 @@ nncmpp
======
'nncmpp' is yet another MPD client. Its specialty is running equally well in
-the terminal, or as an X11 client--it will provide the same keyboard- and
-mouse-friendly interface.
+the terminal, or as an X11 client or macOS application--it will provide the same
+keyboard- and mouse-friendly interface.
This project began its life as a simplified TUI version of Sonata. I had
already written a lot of the required code before, so I had the perfect
@@ -74,8 +74,8 @@ however, tricky to get consistent results on, so be aware of the following:
- Xterm needs `XTerm*metaSendsEscape: true` for the default bindings to work
- urxvt's 'vtwheel' plugin sabotages scrolling
-The X11 graphical interface is a second-class citizen, so some limitations of
-terminals carry over, such as the plain default theme.
+The X11 and macOS graphical interfaces are second-class citizens, so some
+limitations of terminals carry over, such as the plain default theme.
Contributing and Support
------------------------
diff --git a/config.h.in b/config.h.in
index 77296dd..f9fbebd 100644
--- a/config.h.in
+++ b/config.h.in
@@ -10,6 +10,7 @@
#cmakedefine HAVE_RESIZETERM
#cmakedefine WITH_FFTW
#cmakedefine WITH_PULSE
+#cmakedefine WITH_APPKIT
#cmakedefine WITH_X11
#endif /* ! CONFIG_H */
diff --git a/nncmpp.c b/nncmpp.c
index 8f17d8d..4a124a4 100644
--- a/nncmpp.c
+++ b/nncmpp.c
@@ -1,7 +1,7 @@
/*
* nncmpp -- the MPD client you never knew you needed
*
- * Copyright (c) 2016 - 2024, Přemysl Eric Janouch
+ * Copyright (c) 2016 - 2026, Přemysl Eric Janouch
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -74,6 +74,9 @@ enum
#ifdef WITH_X11
#define LIBERTY_XUI_WANT_X11
#endif // WITH_X11
+#ifdef WITH_APPKIT
+#define LIBERTY_XUI_WANT_APPKIT
+#endif // WITH_APPKIT
#include "liberty/liberty-xui.c"
#include
@@ -1910,7 +1913,7 @@ app_layout_status (struct layout *out)
if (!stopped && g.song_elapsed >= 0 && g.song_duration >= 1)
app_push (&l, g.ui->gauge (attrs[0]))
- ->id = WIDGET_GAUGE;
+ ->widget_id = WIDGET_GAUGE;
else
app_push_fill (&l, g.ui->padding (attrs[0], 0, 1));
@@ -1918,7 +1921,7 @@ app_layout_status (struct layout *out)
{
app_push (&l, g.ui->padding (attrs[0], 1, 1));
app_push (&l, g.ui->label (attrs[0], volume.str))
- ->id = WIDGET_VOLUME;
+ ->widget_id = WIDGET_VOLUME;
}
str_free (&volume);
@@ -1934,20 +1937,20 @@ app_layout_tabs (struct layout *out)
// The help tab is disguised so that it's not too intruding
app_push (&l, g.ui->padding (attrs[g.active_tab == g.help_tab], 0.25, 1))
- ->id = WIDGET_TAB;
+ ->widget_id = WIDGET_TAB;
app_push (&l, g.ui->label (attrs[g.active_tab == g.help_tab], APP_TITLE))
- ->id = WIDGET_TAB;
+ ->widget_id = WIDGET_TAB;
// XXX: attrs[0]?
app_push (&l, g.ui->padding (attrs[g.active_tab == g.help_tab], 0.5, 1))
- ->id = WIDGET_TAB;
+ ->widget_id = WIDGET_TAB;
int i = 0;
LIST_FOR_EACH (struct tab, iter, g.tabs)
{
struct widget *w = app_push (&l,
g.ui->label (attrs[iter == g.active_tab], iter->name));
- w->id = WIDGET_TAB;
+ w->widget_id = WIDGET_TAB;
w->userdata = ++i;
}
@@ -1958,7 +1961,7 @@ app_layout_tabs (struct layout *out)
if (g.spectrum_fd != -1)
{
app_push (&l, g.ui->spectrum (attrs[0], g.spectrum.bars))
- ->id = WIDGET_SPECTRUM;
+ ->widget_id = WIDGET_SPECTRUM;
}
#endif // WITH_FFTW
@@ -2060,7 +2063,7 @@ app_layout_view (struct layout *out, int height)
{
struct layout l = {};
struct widget *list = app_push_fill (&l, g.ui->list ());
- list->id = WIDGET_LIST;
+ list->widget_id = WIDGET_LIST;
list->height = height;
list->width = g_xui.width;
@@ -2069,7 +2072,7 @@ app_layout_view (struct layout *out, int height)
{
struct widget *scrollbar = g.ui->scrollbar (APP_ATTR (SCROLLBAR));
list->width -= scrollbar->width;
- app_push (&l, scrollbar)->id = WIDGET_SCROLLBAR;
+ app_push (&l, scrollbar)->widget_id = WIDGET_SCROLLBAR;
}
int to_show = MIN ((int) tab->item_count - tab->item_top,
@@ -2208,7 +2211,7 @@ app_layout_statusbar (struct layout *out)
app_flush_layout (&l, out);
LIST_FOR_EACH (struct widget, w, l.head)
- w->id = WIDGET_MESSAGE;
+ w->widget_id = WIDGET_MESSAGE;
}
else if (g.editor.line)
{
@@ -2230,10 +2233,10 @@ app_layout_statusbar (struct layout *out)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static struct widget *
-app_widget_by_id (int id)
+app_widget_by_id (int widget_id)
{
LIST_FOR_EACH (struct widget, w, g_xui.widgets)
- if (w->id == id)
+ if (w->widget_id == widget_id)
return w;
return NULL;
}
@@ -2891,7 +2894,7 @@ enum { APP_KEYMOD_DOUBLE_CLICK = 1 << 15 };
static bool
app_process_left_mouse_click (struct widget *w, int x, int y, int modifiers)
{
- switch (w->id)
+ switch (w->widget_id)
{
case WIDGET_BUTTON:
app_process_action (w->userdata);
@@ -3002,10 +3005,10 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button,
switch (button)
{
case 1:
- g.ui_dragging = target->id;
+ g.ui_dragging = target->widget_id;
return app_process_left_mouse_click (target, x, y, modifiers);
case 4:
- switch (target->id)
+ switch (target->widget_id)
{
case WIDGET_LIST:
return app_process_action (ACTION_SCROLL_UP);
@@ -3020,7 +3023,7 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button,
}
break;
case 5:
- switch (target->id)
+ switch (target->widget_id)
{
case WIDGET_LIST:
return app_process_action (ACTION_SCROLL_DOWN);
@@ -3343,13 +3346,13 @@ static void
current_tab_move (int from, int to)
{
compact_map_t map;
- const char *id;
+ const char *item_id;
if (!(map = item_list_get (&g.playlist, from))
- || !(id = compact_map_find (map, "id")))
+ || !(item_id = compact_map_find (map, "id")))
return;
char *target_str = xstrdup_printf ("%d", to);
- mpd_client_send_command (&g.client, "moveid", id, target_str, NULL);
+ mpd_client_send_command (&g.client, "moveid", item_id, target_str, NULL);
free (target_str);
}
@@ -3391,7 +3394,7 @@ current_tab_on_action (enum action action)
compact_map_t map = item_list_get (&g.playlist, tab->item_selected);
switch (action)
{
- const char *id;
+ const char *item_id;
case ACTION_GOTO_PLAYING:
if (g.song < 0 || (size_t) g.song >= tab->item_count)
return false;
@@ -3405,13 +3408,13 @@ current_tab_on_action (enum action action)
return current_tab_move_selection (+1);
case ACTION_CHOOSE:
tab->item_mark = -1;
- return map && (id = compact_map_find (map, "id"))
- && MPD_SIMPLE ("playid", id);
+ return map && (item_id = compact_map_find (map, "id"))
+ && MPD_SIMPLE ("playid", item_id);
case ACTION_DESCRIBE:
- if (!map || !(id = compact_map_find (map, "file")))
+ if (!map || !(item_id = compact_map_find (map, "file")))
return false;
- app_show_message (xstrdup ("Path: "), xstrdup (id));
+ app_show_message (xstrdup ("Path: "), xstrdup (item_id));
return true;
case ACTION_DELETE:
{
@@ -3424,8 +3427,8 @@ current_tab_on_action (enum action action)
for (int i = range.from; i <= range.upto; i++)
{
if ((map = item_list_get (&g.playlist, i))
- && (id = compact_map_find (map, "id")))
- mpd_client_send_command (c, "deleteid", id, NULL);
+ && (item_id = compact_map_find (map, "id")))
+ mpd_client_send_command (c, "deleteid", item_id, NULL);
}
mpd_client_list_end (c);
mpd_client_add_task (c, mpd_on_simple_response, NULL);
@@ -5253,12 +5256,12 @@ static ssize_t
mpd_find_pos_of_id (const char *desired_id)
{
compact_map_t map;
- const char *id;
+ const char *item_id;
for (size_t i = 0; i < g.playlist.len; i++)
{
if ((map = item_list_get (&g.playlist, i))
- && (id = compact_map_find (map, "id"))
- && !strcmp (id, desired_id))
+ && (item_id = compact_map_find (map, "id"))
+ && !strcmp (item_id, desired_id))
return i;
}
return -1;
@@ -5565,7 +5568,7 @@ static struct widget *
tui_make_button (chtype attrs, const char *label, enum action a)
{
struct widget *w = tui_make_label (attrs, 0, label);
- w->id = WIDGET_BUTTON;
+ w->widget_id = WIDGET_BUTTON;
w->userdata = a;
return w;
}
@@ -5778,95 +5781,340 @@ static struct app_ui app_tui_ui =
.editor = tui_make_editor,
};
-// --- X11 ---------------------------------------------------------------------
+// --- Shared GUI Icons --------------------------------------------------------
-#ifdef WITH_X11
+#if defined WITH_X11 || defined WITH_APPKIT
+
+struct app_icon_point
+{
+ double x;
+ double y;
+};
// On a 20x20 raster to make it feasible to design on paper.
-#define X11_STOP {INFINITY, INFINITY}
-static const XPointDouble
- x11_icon_previous[] =
+#define APP_ICON_STOP {INFINITY, INFINITY}
+static const struct app_icon_point
+ app_icon_previous[] =
{
- {10, 0}, {0, 10}, {10, 20}, X11_STOP,
- {20, 0}, {10, 10}, {20, 20}, X11_STOP, X11_STOP,
+ {10, 0}, {0, 10}, {10, 20}, APP_ICON_STOP,
+ {20, 0}, {10, 10}, {20, 20}, APP_ICON_STOP, APP_ICON_STOP,
},
- x11_icon_pause[] =
+ app_icon_pause[] =
{
- {1, 0}, {7, 0}, {7, 20}, {1, 20}, X11_STOP,
- {13, 0}, {19, 0}, {19, 20}, {13, 20}, X11_STOP, X11_STOP,
+ {1, 0}, {7, 0}, {7, 20}, {1, 20}, APP_ICON_STOP,
+ {13, 0}, {19, 0}, {19, 20}, {13, 20}, APP_ICON_STOP, APP_ICON_STOP,
},
- x11_icon_play[] =
+ app_icon_play[] =
{
- {0, 0}, {20, 10}, {0, 20}, X11_STOP, X11_STOP,
+ {0, 0}, {20, 10}, {0, 20}, APP_ICON_STOP, APP_ICON_STOP,
},
- x11_icon_stop[] =
+ app_icon_stop[] =
{
- {0, 0}, {20, 0}, {20, 20}, {0, 20}, X11_STOP, X11_STOP,
+ {0, 0}, {20, 0}, {20, 20}, {0, 20}, APP_ICON_STOP, APP_ICON_STOP,
},
- x11_icon_next[] =
+ app_icon_next[] =
{
- {0, 0}, {10, 10}, {0, 20}, X11_STOP,
- {10, 0}, {20, 10}, {10, 20}, X11_STOP, X11_STOP,
+ {0, 0}, {10, 10}, {0, 20}, APP_ICON_STOP,
+ {10, 0}, {20, 10}, {10, 20}, APP_ICON_STOP, APP_ICON_STOP,
},
- x11_icon_repeat[] =
+ app_icon_repeat[] =
{
{0, 12}, {0, 6}, {3, 3}, {13, 3}, {13, 0}, {20, 4.5},
- {13, 9}, {13, 6}, {3, 6}, {3, 10}, X11_STOP,
+ {13, 9}, {13, 6}, {3, 6}, {3, 10}, APP_ICON_STOP,
{0, 15.5}, {7, 11}, {7, 14}, {17, 14}, {17, 10}, {20, 8},
- {20, 14}, {17, 17}, {7, 17}, {7, 20}, X11_STOP, X11_STOP,
+ {20, 14}, {17, 17}, {7, 17}, {7, 20}, APP_ICON_STOP, APP_ICON_STOP,
},
- x11_icon_random[] =
+ app_icon_random[] =
{
- {0, 6}, {0, 3}, {5, 3}, {6, 4.5}, {4, 7.5}, {3, 6}, X11_STOP,
+ {0, 6}, {0, 3}, {5, 3}, {6, 4.5}, {4, 7.5}, {3, 6}, APP_ICON_STOP,
{9, 15.5}, {11, 12.5}, {12, 14}, {13, 14}, {13, 11}, {20, 15.5},
- {13, 20}, {13, 17}, {10, 17}, X11_STOP,
+ {13, 20}, {13, 17}, {10, 17}, APP_ICON_STOP,
{0, 17}, {0, 14}, {3, 14}, {10, 3}, {13, 3}, {13, 0}, {20, 4.5},
- {13, 9}, {13, 6}, {12, 6}, {5, 17}, X11_STOP, X11_STOP,
+ {13, 9}, {13, 6}, {12, 6}, {5, 17}, APP_ICON_STOP, APP_ICON_STOP,
},
- x11_icon_single[] =
+ app_icon_single[] =
{
{7, 6}, {7, 4}, {9, 2}, {12, 2}, {12, 15}, {14, 15}, {14, 18},
- {7, 18}, {7, 15}, {9, 15}, {9, 6}, X11_STOP, X11_STOP,
+ {7, 18}, {7, 15}, {9, 15}, {9, 6}, APP_ICON_STOP, APP_ICON_STOP,
},
- x11_icon_consume[] =
+ app_icon_consume[] =
{
{0, 13}, {0, 7}, {4, 3}, {10, 3}, {14, 7}, {5, 10}, {14, 13},
- {10, 17}, {4, 17}, X11_STOP,
- {16, 12}, {16, 8}, {20, 8}, {20, 12}, X11_STOP, X11_STOP,
+ {10, 17}, {4, 17}, APP_ICON_STOP,
+ {16, 12}, {16, 8}, {20, 8}, {20, 12}, APP_ICON_STOP, APP_ICON_STOP,
};
-static const XPointDouble *
-x11_icon_for_action (enum action action)
+static const struct app_icon_point *
+app_icon_for_action (enum action action)
{
switch (action)
{
case ACTION_MPD_PREVIOUS:
- return x11_icon_previous;
+ return app_icon_previous;
case ACTION_MPD_TOGGLE:
- return g.state == PLAYER_PLAYING ? x11_icon_pause : x11_icon_play;
+ return g.state == PLAYER_PLAYING ? app_icon_pause : app_icon_play;
case ACTION_MPD_STOP:
- return x11_icon_stop;
+ return app_icon_stop;
case ACTION_MPD_NEXT:
- return x11_icon_next;
+ return app_icon_next;
case ACTION_MPD_REPEAT:
- return x11_icon_repeat;
+ return app_icon_repeat;
case ACTION_MPD_RANDOM:
- return x11_icon_random;
+ return app_icon_random;
case ACTION_MPD_SINGLE:
- return x11_icon_single;
+ return app_icon_single;
case ACTION_MPD_CONSUME:
- return x11_icon_consume;
+ return app_icon_consume;
default:
return NULL;
}
}
+#endif // WITH_X11 || WITH_APPKIT
+
+// --- AppKit ------------------------------------------------------------------
+
+#ifdef WITH_APPKIT
+
+static void
+appkit_render_button (struct widget *self)
+{
+ appkit_render_padding (self);
+
+ const struct app_icon_point *icon = app_icon_for_action (self->userdata);
+ if (!icon)
+ {
+ appkit_render_label (self);
+ return;
+ }
+
+ NSColor *color = appkit_fg (self);
+ if (!(self->attrs & A_BOLD))
+ {
+ CGFloat r = 0, g_ = 0, b = 0, a = 1;
+ NSColor *converted =
+ [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
+ if (converted)
+ {
+ [converted getRed:&r green:&g_ blue:&b alpha:&a];
+ color = [NSColor colorWithDeviceRed:r * 0.5
+ green:g_ * 0.5 blue:b * 0.5 alpha:a * 0.5];
+ }
+ }
+
+ [color setFill];
+ NSBezierPath *path = [NSBezierPath bezierPath];
+ [path setWindingRule:NSWindingRuleEvenOdd];
+
+ int x = self->x, y = self->y + (self->height - self->width) / 2;
+ bool started = false;
+ for (size_t i = 0; ; i++)
+ {
+ const struct app_icon_point *p = &icon[i];
+ if (isinf (p->x))
+ {
+ if (!started)
+ break;
+ [path closePath];
+ started = false;
+ continue;
+ }
+
+ NSPoint point = NSMakePoint
+ (x + p->x / 20.0 * self->width, y + p->y / 20.0 * self->width);
+ if (!started)
+ [path moveToPoint:point];
+ else
+ [path lineToPoint:point];
+ started = true;
+ }
+ [path fill];
+}
+
+static struct widget *
+appkit_make_button (chtype attrs, const char *label, enum action a)
+{
+ struct widget *w = appkit_make_label (attrs, 0, label);
+ w->widget_id = WIDGET_BUTTON;
+ w->userdata = a;
+
+ if (app_icon_for_action (a))
+ {
+ w->on_render = appkit_render_button;
+
+ // It should be padded by the caller horizontally.
+ w->height = g_xui.vunit;
+ w->width = w->height * 3 / 4;
+ }
+ return w;
+}
+
+static void
+appkit_render_gauge (struct widget *self)
+{
+ appkit_render_padding (self);
+ if (g.state == PLAYER_STOPPED || g.song_elapsed < 0 || g.song_duration < 1)
+ return;
+
+ int part = (float) g.song_elapsed / g.song_duration * self->width;
+ [appkit_bg_attrs (APP_ATTR (ELAPSED)) setFill];
+ NSRectFill (NSMakeRect
+ (self->x, self->y + self->height / 8, part, self->height * 3 / 4));
+ [appkit_bg_attrs (APP_ATTR (REMAINS)) setFill];
+ NSRectFill (NSMakeRect (self->x + part, self->y + self->height / 8,
+ self->width - part, self->height * 3 / 4));
+}
+
+static struct widget *
+appkit_make_gauge (chtype attrs)
+{
+ struct widget *w = xcalloc (1, sizeof *w + 1);
+ w->on_render = appkit_render_gauge;
+ w->attrs = attrs;
+ w->width = -1;
+ w->height = g_xui.vunit;
+ return w;
+}
+
+static void
+appkit_render_spectrum (struct widget *self)
+{
+ appkit_render_padding (self);
+
+#ifdef WITH_FFTW
+ int bars = g.spectrum.bars;
+ if (bars < 1)
+ return;
+
+ int step = MAX (1, self->width / bars);
+ [appkit_fg (self) setFill];
+ for (int i = 0; i < bars; i++)
+ {
+ int height = round ((self->height - 2) * g.spectrum.spectrum[i]);
+ NSRectFill (NSMakeRect (self->x + i * step,
+ self->y + self->height - 1 - height,
+ step, height));
+ }
+#endif // WITH_FFTW
+}
+
+static struct widget *
+appkit_make_spectrum (chtype attrs, int width)
+{
+ struct widget *w = xcalloc (1, sizeof *w + 1);
+ w->on_render = appkit_render_spectrum;
+ w->attrs = attrs;
+ w->width = width * g_xui.vunit / 2;
+ w->height = g_xui.vunit;
+ return w;
+}
+
+static void
+appkit_render_scrollbar (struct widget *self)
+{
+ appkit_render_padding (self);
+
+ struct tab *tab = g.active_tab;
+ struct scrollbar bar =
+ app_compute_scrollbar (tab, app_visible_items_height (), g_xui.vunit);
+
+ [appkit_fg_attrs (self->attrs) setFill];
+ NSRectFill (NSMakeRect
+ (self->x, self->y + bar.start, self->width, bar.length));
+}
+
+static struct widget *
+appkit_make_scrollbar (chtype attrs)
+{
+ struct widget *w = xcalloc (1, sizeof *w + 1);
+ w->on_render = appkit_render_scrollbar;
+ w->attrs = attrs;
+ w->width = g_xui.vunit / 2;
+ return w;
+}
+
+static struct widget *
+appkit_make_list (void)
+{
+ struct widget *w = xcalloc (1, sizeof *w + 1);
+ w->on_render = appkit_render_padding;
+ return w;
+}
+
+static void
+appkit_render_editor (struct widget *self)
+{
+ appkit_render_padding (self);
+
+ NSFont *font = appkit_widget_font (self);
+ NSColor *color = appkit_fg (self);
+
+ const struct line_editor *e = &g.editor;
+ int x = self->x;
+ if (e->prompt)
+ {
+ hard_assert (e->prompt < 127);
+ x += appkit_font_draw (font, color, x, self->y,
+ (char[2]) { e->prompt, 0 }, self->width) + g_xui.vunit / 4;
+ }
+
+ size_t len;
+ ucs4_t *buf = xcalloc (e->len + 1, sizeof *buf);
+ u32_cpy (buf, e->line, e->point);
+ char *a = (char *) u32_to_u8 (buf, u32_strlen (buf) + 1, NULL, &len);
+ u32_cpy (buf, e->line + e->point, e->len - e->point + 1);
+ char *b = (char *) u32_to_u8 (buf, u32_strlen (buf) + 1, NULL, &len);
+ free (buf);
+
+ x += appkit_font_draw (font, color, x, self->y, a,
+ MAX (0, self->width - (x - self->x)));
+ int caret = x;
+ x += appkit_font_draw (font, color, x, self->y, b,
+ MAX (0, self->width - (x - self->x)));
+ free (a);
+ free (b);
+
+ [color setFill];
+ NSRectFill (NSMakeRect (caret, self->y, 2, self->height));
+}
+
+static struct widget *
+appkit_make_editor (chtype attrs)
+{
+ struct widget *w = xcalloc (1, sizeof *w + 1);
+ w->on_render = appkit_render_editor;
+ w->attrs = attrs;
+ w->width = -1;
+ w->height = g_xui.vunit;
+ return w;
+}
+
+static struct app_ui app_appkit_ui =
+{
+ .padding = appkit_make_padding,
+ .label = app_make_label,
+ .button = appkit_make_button,
+ .gauge = appkit_make_gauge,
+ .spectrum = appkit_make_spectrum,
+ .scrollbar = appkit_make_scrollbar,
+ .list = appkit_make_list,
+ .editor = appkit_make_editor,
+
+ .have_icons = true,
+};
+
+#endif // WITH_APPKIT
+
+// --- X11 ---------------------------------------------------------------------
+
+#ifdef WITH_X11
+
static void
x11_render_button (struct widget *self)
{
x11_render_padding (self);
- const XPointDouble *icon = x11_icon_for_action (self->userdata);
+ const struct app_icon_point *icon = app_icon_for_action (self->userdata);
if (!icon)
{
x11_render_label (self);
@@ -5874,8 +6122,15 @@ x11_render_button (struct widget *self)
}
size_t total = 0;
- for (size_t i = 0; icon[i].x != INFINITY || icon[i - 1].x != INFINITY; i++)
- total++;
+ for (; ; total++)
+ {
+ if (isinf (icon[total].x) && isinf (icon[total].y)
+ && isinf (icon[total + 1].x) && isinf (icon[total + 1].y))
+ {
+ total++;
+ break;
+ }
+ }
// TODO: There should be an attribute for buttons, to handle this better.
XRenderColor color = *x11_fg (self);
@@ -5914,10 +6169,10 @@ static struct widget *
x11_make_button (chtype attrs, const char *label, enum action a)
{
struct widget *w = x11_make_label (attrs, 0, label);
- w->id = WIDGET_BUTTON;
+ w->widget_id = WIDGET_BUTTON;
w->userdata = a;
- if (x11_icon_for_action (a))
+ if (app_icon_for_action (a))
{
w->on_render = x11_render_button;
@@ -6112,10 +6367,10 @@ static volatile sig_atomic_t g_termination_requested;
static volatile sig_atomic_t g_winch_received;
static void
-signals_postpone_handling (char id)
+signals_postpone_handling (char signal_id)
{
int original_errno = errno;
- if (write (g_signal_pipe[1], &id, 1) == -1)
+ if (write (g_signal_pipe[1], &signal_id, 1) == -1)
soft_assert (errno == EAGAIN);
errno = original_errno;
}
@@ -6176,8 +6431,8 @@ app_on_signal_pipe_readable (const struct pollfd *fd, void *user_data)
{
(void) user_data;
- char id = 0;
- (void) read (fd->fd, &id, 1);
+ char signal_id = 0;
+ (void) read (fd->fd, &signal_id, 1);
if (g_termination_requested && !g.quitting)
app_quit ();
@@ -6277,6 +6532,11 @@ app_init_ui (bool requested_x11)
g.ui = &app_x11_ui;
else
#endif // WITH_X11
+#ifdef WITH_APPKIT
+ if (g_xui.ui == &appkit_ui)
+ g.ui = &app_appkit_ui;
+ else
+#endif // WITH_APPKIT
g.ui = &app_tui_ui;
}
@@ -6308,9 +6568,9 @@ main (int argc, char *argv[])
static const struct opt opts[] =
{
{ 'd', "debug", NULL, 0, "run in debug mode" },
-#ifdef WITH_X11
- { 'x', "x11", NULL, 0, "use X11 even when run from a terminal" },
-#endif // WITH_X11
+#if defined WITH_X11 || defined WITH_APPKIT
+ { 'x', "x11", NULL, 0, "use a graphical frontend even from a terminal" },
+#endif // WITH_X11 || WITH_APPKIT
{ 'h', "help", NULL, 0, "display this help and exit" },
{ 'v', "verbose", NULL, 0, "log messages on standard error" },
{ 'V', "version", NULL, 0, "output version information and exit" },
@@ -6328,9 +6588,11 @@ main (int argc, char *argv[])
case 'd':
g_debug_mode = true;
break;
+#if defined WITH_X11 || defined WITH_APPKIT
case 'x':
requested_x11 = true;
break;
+#endif // WITH_X11 || WITH_APPKIT
case 'v':
g_verbose_mode = true;
break;
--
cgit v1.2.3-70-g09d2