diff options
| author | Přemysl Eric Janouch <p@janouch.name> | 2022-01-09 18:36:27 +0100 | 
|---|---|---|
| committer | Přemysl Eric Janouch <p@janouch.name> | 2022-01-09 19:11:36 +0100 | 
| commit | 4f19a67da34ff39c5a04a6a8ab59a4df0d54e540 (patch) | |
| tree | c44c9d7b2f756d855628362eb133f1b2b9c93fb8 | |
| parent | 12e3c42888879275c53e9044749409af62f2cd88 (diff) | |
| download | fiv-4f19a67da34ff39c5a04a6a8ab59a4df0d54e540.tar.gz fiv-4f19a67da34ff39c5a04a6a8ab59a4df0d54e540.tar.xz fiv-4f19a67da34ff39c5a04a6a8ab59a4df0d54e540.zip  | |
Add an unnecessarily fancy about dialog
| -rw-r--r-- | fiv-browser.c | 2 | ||||
| -rw-r--r-- | fiv.c | 257 | ||||
| -rw-r--r-- | resources/resources.gresource.xml | 3 | 
3 files changed, 261 insertions, 1 deletions
diff --git a/fiv-browser.c b/fiv-browser.c index d58e180..175f364 100644 --- a/fiv-browser.c +++ b/fiv-browser.c @@ -989,7 +989,7 @@ fiv_browser_button_press_event(GtkWidget *widget, GdkEventButton *event)  	}  } -gboolean +static gboolean  fiv_browser_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)  {  	GTK_WIDGET_CLASS(fiv_browser_parent_class) @@ -206,6 +206,252 @@ make_key_window(void)  	return window;  } +// --- About ------------------------------------------------------------------- + +static void +on_about_map(GtkWidget *widget, G_GNUC_UNUSED gpointer user_data) +{ +	GdkFrameClock *clock = gtk_widget_get_frame_clock(widget); +	(void) g_signal_connect_swapped( +		clock, "update", G_CALLBACK(gtk_widget_queue_draw), widget); +	gdk_frame_clock_begin_updating(clock); +} + +static void +on_about_unmap(GtkWidget *widget, G_GNUC_UNUSED gpointer user_data) +{ +	GdkFrameClock *clock = gtk_widget_get_frame_clock(widget); +	gdk_frame_clock_end_updating(clock); +} + +static gint g_about_x, g_about_y; + +static gboolean +on_about_motion(G_GNUC_UNUSED GtkWidget *widget, GdkEventMotion *event, +	G_GNUC_UNUSED gpointer user_data) +{ +	g_about_x = event->x; +	g_about_y = event->y; +	return FALSE; +} + +static gboolean +on_about_leave(G_GNUC_UNUSED GtkWidget *widget, +	G_GNUC_UNUSED GdkEventCrossing *event, G_GNUC_UNUSED gpointer user_data) +{ +	g_about_x = 0; +	g_about_y = 0; +	return FALSE; +} + +enum { ABOUT_SIZE = 48, ABOUT_SCALE = 3, ABOUT_HEIGHT = ABOUT_SIZE * 4 / 3 }; + +// The mismatching resolution is incidental, and kept for interesting looks. +cairo_pattern_t * +make_infinite_v_pattern(void) +{ +	cairo_surface_t *surface = +		cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ABOUT_SIZE, ABOUT_SIZE); +	cairo_t *cr = cairo_create(surface); +	cairo_move_to(cr, 2, 7); +	cairo_rel_line_to(cr, 44, 0); +	cairo_rel_line_to(cr, -17, 39); +	cairo_rel_line_to(cr, -10, 0); +	cairo_close_path(cr); + +	cairo_pattern_t *gradient = cairo_pattern_create_linear(0, 7, 0, 46); +	cairo_pattern_add_color_stop_rgba(gradient, 1, 1, 0x66 / 255., 0, 1); +	cairo_pattern_add_color_stop_rgba(gradient, 0, 1, 0xaa / 255., 0, 1); +	cairo_set_source(cr, gradient); +	cairo_fill(cr); + +	cairo_destroy(cr); +	cairo_pattern_t *pattern = cairo_pattern_create_for_surface(surface); +	cairo_surface_destroy(surface); +	cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); +	cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); +	cairo_matrix_t matrix = {}; +	cairo_matrix_init_translate(&matrix, ABOUT_SIZE / 2, 0); +	cairo_pattern_set_matrix(pattern, &matrix); +	return pattern; +} + +static void +draw_ligature(cairo_t *cr) +{ +	// Transcribed fiv.svg--it would arguably make more sense to generate +	// that file and these Cairo instructions from the same source. +	cairo_move_to(cr, 12.5, 20.5); +	cairo_rel_line_to(cr, -4, 0); +	cairo_rel_line_to(cr, 0, +5); +	cairo_rel_line_to(cr, +4, 0); +	cairo_rel_line_to(cr, 0, +15); +	cairo_rel_line_to(cr, 9, 0); +	cairo_rel_line_to(cr, 0, -15); +	cairo_rel_line_to(cr, +2, 0); +	cairo_rel_line_to(cr, 0, -5); +	cairo_rel_line_to(cr, -2, 0); +	cairo_rel_line_to(cr, 0, -8); +	cairo_rel_curve_to(cr, 0, -4, 5, -4, 5, 0); +	cairo_rel_curve_to(cr, 0, 6, 9, 6, 9, 0); +	cairo_rel_curve_to(cr, 0, -12, -23, -12, -23, 0); +	cairo_close_path(cr); + +	cairo_move_to(cr, 26.5, 20.5); +	cairo_rel_line_to(cr, 9, 0); +	cairo_rel_line_to(cr, 0, 20); +	cairo_rel_line_to(cr, -9, 0); +	cairo_close_path(cr); + +	cairo_path_t *ligature = cairo_copy_path(cr); +	cairo_save(cr); +	cairo_clip(cr); + +	// Shadow approximation, given the lack of filters in Cairo. +	enum { STEPS = 5 }; +	for (int i = 0; i <= STEPS; i++) { +		cairo_save(cr); +		double o = 1. / ABOUT_SCALE + 1. / ABOUT_SCALE * i; +		cairo_translate(cr, o, o); +		cairo_append_path(cr, ligature); +		double v = 1 - (STEPS - i) * 0.075; +		cairo_set_source_rgb(cr, v, v, v); +		cairo_fill(cr); +		cairo_restore(cr); +	} + +	cairo_restore(cr); +	cairo_append_path(cr, ligature); +	cairo_path_destroy(ligature); +	cairo_set_source_rgb(cr, 0, 0, 0); +	cairo_set_line_width(cr, 1); +	cairo_stroke(cr); +} + +static gboolean +on_about_draw(GtkWidget *widget, cairo_t *cr, G_GNUC_UNUSED gpointer user_data) +{ +	GtkAllocation allocation; +	gtk_widget_get_allocation(widget, &allocation); +	GtkStyleContext *style = gtk_widget_get_style_context(widget); +	gtk_render_background(style, cr, 0, 0, allocation.width, allocation.height); + +	cairo_translate(cr, (allocation.width - ABOUT_SIZE * ABOUT_SCALE) / 2, +		ABOUT_SIZE * ABOUT_SCALE / 4); +	cairo_scale(cr, ABOUT_SCALE, ABOUT_SCALE); + +	cairo_save(cr); +	cairo_translate(cr, ABOUT_SIZE / 2, ABOUT_SIZE / 2); +	if (g_about_x && g_about_y) { +		gint dx = g_about_x - allocation.width / 2; +		gint dy = g_about_y - ABOUT_SIZE * ABOUT_SCALE * 3 / 4; +		cairo_rotate(cr, atan2(dy, dx) - M_PI_2); +	} + +	GdkFrameClock *clock = gtk_widget_get_frame_clock(widget); +	gint64 t = gdk_frame_clock_get_frame_time(clock); +	cairo_translate(cr, 0, (gint64) (t / 4e4) % ABOUT_SIZE); + +	cairo_pattern_t *v = make_infinite_v_pattern(); +	cairo_set_source(cr, v); +	cairo_paint(cr); + +	cairo_save(cr); +	cairo_translate(cr, ABOUT_SIZE / 2, 14 /* Through trial and error. */); +	cairo_scale(cr, 1, -1); +	cairo_set_source(cr, v); +	cairo_paint(cr); +	cairo_restore(cr); + +	cairo_pattern_destroy(v); +	cairo_restore(cr); +	draw_ligature(cr); +	return TRUE; +} + +static GtkWidget * +make_about_dialog(GtkWidget *parent) +{ +	GtkWidget *dialog = gtk_widget_new(GTK_TYPE_DIALOG, "use-header-bar", TRUE, +		"title", "About", "transient-for", parent, "destroy-with-parent", TRUE, +		NULL); + +	GtkWidget *area = gtk_drawing_area_new(); +	gtk_style_context_add_class(gtk_widget_get_style_context(area), "view"); +	gtk_widget_set_size_request( +		area, ABOUT_SIZE * ABOUT_SCALE * 2, ABOUT_HEIGHT * ABOUT_SCALE); + +	gtk_widget_add_events( +		area, GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK); +	g_signal_connect( +		area, "motion-notify-event", G_CALLBACK(on_about_motion), NULL); +	g_signal_connect( +		area, "leave-notify-event", G_CALLBACK(on_about_leave), NULL); + +	g_signal_connect(area, "draw", G_CALLBACK(on_about_draw), NULL); +	g_signal_connect(area, "map", G_CALLBACK(on_about_map), NULL); +	g_signal_connect(area, "unmap", G_CALLBACK(on_about_unmap), NULL); + +	// The rest is approximately copying GTK+'s own gtkaboutdialog.ui. +	GtkWidget *name = gtk_label_new(NULL); +	gtk_label_set_selectable(GTK_LABEL(name), TRUE); +	gtk_label_set_markup( +		GTK_LABEL(name), "<b>" PROJECT_NAME "</b> " PROJECT_VERSION); + +	GtkWidget *website = gtk_label_new(NULL); +	gtk_label_set_selectable(GTK_LABEL(website), TRUE); +	const char *url = "https://git.janouch.name/p/fiv"; +	gchar *link = g_strdup_printf("<a href='%s'>%s</a>", url, url); +	gtk_label_set_markup(GTK_LABEL(website), link); +	g_free(link); + +	GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL); +	gtk_scrolled_window_set_policy( +		GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); +	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN); + +	GtkWidget *viewer = gtk_text_view_new(); +	gtk_text_view_set_editable(GTK_TEXT_VIEW(viewer), FALSE); +	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(viewer), FALSE); +	gtk_text_view_set_left_margin(GTK_TEXT_VIEW(viewer), 8); +	gtk_text_view_set_right_margin(GTK_TEXT_VIEW(viewer), 8); +	gtk_container_add(GTK_CONTAINER(sw), viewer); + +	GBytes *license = +		g_resources_lookup_data("/LICENSE", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL); +	g_return_val_if_fail(license != NULL, dialog); +	gchar *escaped = g_markup_escape_text(g_bytes_get_data(license, NULL), -1); +	g_bytes_unref(license); + +	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(viewer)); +	gchar *formatted = g_strdup_printf("<small>%s</small>", escaped); +	g_free(escaped); +	GtkTextIter iter = {}; +	gtk_text_buffer_get_start_iter(buffer, &iter); +	gtk_text_buffer_insert_markup(buffer, &iter, formatted, -1); +	g_free(formatted); + +	enum { SUBBOX_MARGIN = 12 /* Roughly matches. */ }; +	GtkWidget *subbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); +	gtk_widget_set_margin_start(subbox, SUBBOX_MARGIN); +	gtk_widget_set_margin_end(subbox, SUBBOX_MARGIN); +	gtk_box_pack_start(GTK_BOX(subbox), name, FALSE, FALSE, 0); +	gtk_box_pack_start(GTK_BOX(subbox), website, FALSE, FALSE, 0); +	gtk_box_pack_start(GTK_BOX(subbox), sw, TRUE, TRUE, 0); + +	GtkWidget *box = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); +	gtk_box_pack_start(GTK_BOX(box), area, FALSE, FALSE, 0); +	gtk_box_pack_end(GTK_BOX(box), subbox, TRUE, TRUE, SUBBOX_MARGIN); + +	gtk_window_set_default_size(GTK_WINDOW(dialog), -1, 480); +	gtk_window_set_geometry_hints(GTK_WINDOW(dialog), NULL, +		&(GdkGeometry) {.max_width = -1, .max_height = G_MAXSHORT}, +		GDK_HINT_MAX_SIZE); + +	gtk_widget_grab_focus(viewer); +	return dialog; +} +  // --- Main --------------------------------------------------------------------  // TODO(p): See if it's possible to give separators room to shrink @@ -820,6 +1066,17 @@ on_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,  			return TRUE;  		}  		break; +	case GDK_SHIFT_MASK: +		switch (event->keyval) { +		case GDK_KEY_F1: { +			GtkWidget *dialog = make_about_dialog(g.window); +			gtk_widget_show_all(dialog); +			gtk_dialog_run(GTK_DIALOG(dialog)); +			gtk_widget_destroy(dialog); +			return TRUE; +		} +		} +		break;  	case 0:  		switch (event->keyval) {  		case GDK_KEY_Escape: diff --git a/resources/resources.gresource.xml b/resources/resources.gresource.xml index 0b32dc8..092aed1 100644 --- a/resources/resources.gresource.xml +++ b/resources/resources.gresource.xml @@ -1,5 +1,8 @@  <?xml version="1.0" encoding="UTF-8"?>  <gresources> +	<gresource prefix="/"> +		<file alias="LICENSE">../LICENSE</file> +	</gresource>  	<gresource prefix="/org/gnome/design/IconLibrary/scalable/actions/">  		<file preprocess="xml-stripblanks">circle-filled-symbolic.svg</file>  		<file preprocess="xml-stripblanks">funnel-symbolic.svg</file>  | 
