per-source caps, hlist, and memory region selector

This commit is contained in:
Lephenixnoir 2022-06-24 20:08:47 +01:00
parent ffa2199824
commit 9f1d806af8
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
12 changed files with 385 additions and 17 deletions

View File

@ -12,10 +12,12 @@ find_package(JustUI 1.0 REQUIRED)
set(SOURCES
src/buffer.c
src/heditor.c
src/hlist.c
src/main.c
src/source.c
src/source-lazy-file.c
src/source-loaded-file.c
src/source-memory.c
src/util.c
)
set(ASSETS

View File

@ -164,7 +164,7 @@ bool heditor_save(heditor *e, char const *outfile)
void heditor_set_insert_mode(heditor *e, bool insert)
{
e->insert = false;
if(e->source && e->source->intf->cap & SOURCE_RESIZE)
if(e->source && e->source->cap & SOURCE_RESIZE)
e->insert = insert;
shake(e);
e->widget.update = true;
@ -345,9 +345,9 @@ static bool heditor_poly_event(void *e0, jevent ev)
// Edition //
if(!(e->source->intf->cap & SOURCE_WRITE))
if(!(e->source->cap & SOURCE_WRITE))
return false;
if(e->insert && !(e->source->intf->cap & SOURCE_RESIZE))
if(e->insert && !(e->source->cap & SOURCE_RESIZE))
return false;
int hexdigit = get_hexdigit(key);

170
src/hlist.c Normal file
View File

@ -0,0 +1,170 @@
#include "hlist.h"
#include <justui/jwidget.h>
#include <justui/jwidget-api.h>
#include <gint/display.h>
#include <stdlib.h>
/* Type identifier for hlist */
static int hlist_type_id = -1;
/* Events */
uint16_t HLIST_VALIDATED;
uint16_t HLIST_CANCELED;
hlist *hlist_create(void *parent)
{
if(hlist_type_id < 0) return NULL;
hlist *l = malloc(sizeof *l);
if(!l) return NULL;
jwidget_init(&l->widget, hlist_type_id, parent);
l->entries = NULL;
l->entry_size = 0;
l->entry_count = 0;
l->render_height = 0;
l->render_func = NULL;
l->selected_index = -1;
l->cursor = 0;
l->scroll = 0;
l->visible_lines = 0;
return l;
}
static void count_visible_lines(hlist *l)
{
int ch = jwidget_content_height(l);
l->visible_lines = ch / l->render_height;
}
void hlist_set_entries(hlist *l, void *entries, size_t entry_size, int count)
{
l->entries = entries;
l->entry_size = entry_size;
l->entry_count = count;
l->cursor = 0;
l->scroll = 0;
l->widget.update = true;
}
void hlist_set_renderer(hlist *l, int render_height, void *render)
{
if(l->render_height != render_height)
l->widget.dirty = true;
l->render_height = render_height;
l->render_func = render;
l->widget.update = true;
}
int hlist_selected_index(hlist *l)
{
return l->selected_index;
}
// Polymorphic widget operations //
static void hlist_poly_csize(void *l0)
{
hlist *l = l0;
int lines = l->entry_count;
int lheight = l->render_height;
l->widget.w = 128;
l->widget.h = (lines && lheight) ? lines * lheight : 64;
}
static void hlist_poly_layout(void *l0)
{
hlist *l = l0;
count_visible_lines(l);
}
static void hlist_poly_render(void *l0, int x, int y)
{
hlist *l = l0;
if(!l->entries)
return;
int cw = jwidget_content_width(l);
for(int i = 0; i < l->visible_lines && l->scroll+i < l->entry_count; i++) {
bool selected = (l->cursor == l->scroll + i);
void *entry = l->entries + l->entry_size * i;
int line_y = y + l->render_height * i;
if(l->render_func)
l->render_func(x, line_y, cw, l->render_height, entry, selected);
}
}
static bool hlist_poly_event(void *l0, jevent e)
{
hlist *l = l0;
if(!l->entries || e.type != JWIDGET_KEY)
return false;
key_event_t ev = e.key;
if(ev.type != KEYEV_DOWN && ev.type != KEYEV_HOLD)
return false;
int key = ev.key;
bool moved = false;
if(key == KEY_UP && l->cursor > 0) {
l->cursor = ev.shift ? 0 : l->cursor - 1;
moved = true;
}
if(key == KEY_DOWN && l->cursor < l->entry_count - 1) {
l->cursor = ev.shift ? l->entry_count - 1 : l->cursor + 1;
moved = true;
}
if(l->scroll > 0 && l->cursor <= l->scroll)
l->scroll = max(l->cursor - 1, 0);
if(l->scroll + l->visible_lines < l->entry_count
&& l->cursor >= l->scroll + l->visible_lines - 2) {
l->scroll = min(l->cursor - l->visible_lines + 2,
l->entry_count - l->visible_lines);
}
if(moved) {
l->widget.update = true;
return true;
}
if(key == KEY_EXIT) {
jwidget_emit(l, (jevent){ .type = HLIST_CANCELED });
return true;
}
else if(key == KEY_EXE) {
l->selected_index = l->cursor;
jwidget_emit(l, (jevent){ .type = HLIST_VALIDATED });
return true;
}
return false;
}
/* hlist type definition */
static jwidget_poly type_hlist = {
.name = "hlist",
.csize = hlist_poly_csize,
.layout = hlist_poly_layout,
.render = hlist_poly_render,
.event = hlist_poly_event,
};
__attribute__((constructor(1004)))
static void j_register_hlist(void)
{
hlist_type_id = j_register_widget(&type_hlist, "jwidget");
HLIST_VALIDATED = j_register_event();
HLIST_CANCELED = j_register_event();
}

61
src/hlist.h Normal file
View File

@ -0,0 +1,61 @@
// hlist: A basic list selector
//
// This is like jfileselect, but for custom items. And also, maybe, a base for
// a reasonably-generic list view backed by a reasonable list model. Who knows.
#pragma once
#include <justui/defs.h>
#include <justui/jwidget.h>
#include <gint/display.h>
/* hlist: Generic list view and selector
Events:
* HLIST_VALIDATED
* HLIST_CANCELED */
typedef struct {
jwidget widget;
/* List of entries; size of individual entries; number of entries */
void *entries;
size_t entry_size;
int entry_count;
/* Height in pixels for each entry */
int render_height;
/* Renderer function */
void (*render_func)(int x, int y, int w, int h, void *entry,bool selected);
/* Selected entry ID */
int selected_index;
/* Current cursor position (0 .. entry_count-1) */
int16_t cursor;
/* Current scroll position */
int16_t scroll;
/* Number of visible lines */
int8_t visible_lines;
} hlist;
/* Event IDs */
extern uint16_t HLIST_VALIDATED;
extern uint16_t HLIST_CANCELED;
/* hlist_create(): Create a list view and selector
Initially, there is no data and the widget does not handle events. */
hlist *hlist_create(void *parent);
/* hlist_set_entries(): Load the model (list of entries) */
void hlist_set_entries(hlist *l, void *entries, size_t entry_size, int count);
/* hlist_set_renderer(): Set the rendering settings for the entries
The rendering function should be of type:
void render(int x, int y, int width, int height,
void *pointer_to_entry, bool selected); */
void hlist_set_renderer(hlist *h, int entry_height, void *render);
/* hlist_selected_index(): Index of the selected entry
The selected index is -1 until hlist_set_entries() is called and the user
selects an entry in the interface. */
int hlist_selected_index(hlist *l);

View File

@ -1,5 +1,6 @@
#include "source.h"
#include "heditor.h"
#include "hlist.h"
#include "util.h"
#include <gint/display.h>
@ -78,7 +79,7 @@ static void update_fkeys(void)
if(!app.source)
jfkeys_set(app.fkeys, "/OPEN;;;;;");
else {
bool can_insert = (app.source->intf->cap & SOURCE_RESIZE) != 0;
bool can_insert = (app.source->cap & SOURCE_RESIZE) != 0;
fkeys_sprintf(app.fkeys, "/OPEN;/SOURCE;%s%s;@GOTO;;#VIEW",
can_insert ? "@" : "#",
app.insert ? "INSERT" : "OVERW");
@ -87,7 +88,7 @@ static void update_fkeys(void)
else if(app.fkmenu == FK_EDITOR_SOURCE) {
fkeys_sprintf(app.fkeys, "%s;%s;;;;",
!app.source->no_origin ? "@SAVE" : "",
app.source->intf->cap & SOURCE_SAVEAS ? "@SAVE AS" : "");
app.source->cap & SOURCE_SAVEAS ? "@SAVE AS" : "");
}
else if(app.fkmenu == FK_EDITOR_OPEN) {
jfkeys_set(app.fkeys, "@FILE;@LAZY;@MEMORY;@NEW");
@ -151,11 +152,11 @@ static void update_source_info(void)
char caps[48];
memset(caps, 0, sizeof caps);
if(s->intf->cap & SOURCE_WRITE)
if(s->cap & SOURCE_WRITE)
strcat(caps, " WRITE");
if(s->intf->cap & SOURCE_RESIZE)
if(s->cap & SOURCE_RESIZE)
strcat(caps, " RESIZE");
if(s->intf->cap & SOURCE_SAVEAS)
if(s->cap & SOURCE_SAVEAS)
strcat(caps, " SAVEAS");
jlabel_asprintf(app.source_info,
@ -253,6 +254,23 @@ static void open_source(source_t *source, bool insert)
update_status();
}
static void render_memory_entry(int x, int y, int w, int h,
memory_region_t **r_ptr, bool selected)
{
memory_region_t *r = *r_ptr;
int fg = C_BLACK;
if(selected) {
drect(x, y, x+w-1, y+h-1, C_BLACK);
fg = C_WHITE;
}
dprint(x+2, y+2, fg, "%08X %s", r->start, r->name);
dprint(x+3*w/4, y+2, fg, "(%s)", r->writable ? "rw" : "r");
dtext_opt(x+w-3, y+2, fg, C_NONE, DTEXT_RIGHT, DTEXT_TOP,
human_isize(r->end - r->start, NULL), -1);
}
void hex_view(void)
{
memset(&app, 0, sizeof app);
@ -318,6 +336,17 @@ void hex_view(void)
jfileselect_set_show_file_size(fileselect, true);
jwidget_set_stretch(fileselect, 1, 1, false);
// Memory region selection tab //
int memory_region_count = 0;
while(memory_all_regions[memory_region_count]) memory_region_count++;
hlist *memoryselect = hlist_create(stack);
jwidget_set_stretch(memoryselect, 1, 1, false);
hlist_set_entries(memoryselect, memory_all_regions,
sizeof *memory_all_regions, memory_region_count);
hlist_set_renderer(memoryselect, 13, render_memory_entry);
// Info tab //
jwidget *tab_info = jwidget_create(stack);
@ -380,7 +409,7 @@ void hex_view(void)
char const *path = jfileselect_current_folder(fileselect);
jlabel_asprintf(title, "Browsing: %s", path ? path : "(nil)");
}
if(e.type == JFILESELECT_CANCELED) {
if(e.type == JFILESELECT_CANCELED || e.type == HLIST_CANCELED) {
jscene_show_and_focus(scene, mem);
update_status();
app.fkmenu = FK_EDITOR;
@ -410,6 +439,15 @@ void hex_view(void)
if(e.type == HEDITOR_CHANGED) {
update_status();
}
if(e.type == HLIST_VALIDATED) {
int i = hlist_selected_index(memoryselect);
if(i >= 0) {
if(app.source)
source_free(app.source);
open_source(source_memory_open(memory_all_regions[i]), false);
jscene_show_and_focus(scene, mem);
}
}
// Fkey menu navigation //
@ -441,7 +479,7 @@ void hex_view(void)
update_fkeys();
}
else if(key == KEY_F3 && app.source) {
if(app.source->intf->cap & SOURCE_RESIZE) {
if(app.source->cap & SOURCE_RESIZE) {
app.insert = !app.insert;
heditor_set_insert_mode(mem, app.insert);
update_fkeys();
@ -464,7 +502,7 @@ void hex_view(void)
}
}
else if(key == KEY_F2 && app.source) {
if(app.source->intf->cap & SOURCE_SAVEAS) {
if(app.source->cap & SOURCE_SAVEAS) {
browser_mode = SAVE_AS;
jfileselect_set_saveas(fileselect, true);
jfileselect_browse(fileselect, "/");
@ -496,7 +534,11 @@ void hex_view(void)
scene->widget.update = true;
}
else if(key == KEY_F3) {
/* TODO: Memory browser */
if(!has_unsaved_changes() || confirm_discard()) {
jscene_show_and_focus(scene, memoryselect);
jlabel_set_text(title, "Browsing: memory");
}
scene->widget.update = true;
}
else if(key == KEY_F4) {
if(!has_unsaved_changes() || confirm_discard()) {

View File

@ -77,7 +77,6 @@ static void lz_close(void *_cookie)
static source_intf_t lz_intf = {
.name = "Lazy File",
.cap = SOURCE_WRITE,
.close_on_return_to_menu = true,
.read = lz_read,
.write = lz_write,
@ -104,6 +103,7 @@ source_t *source_lazy_file_open(char const *path)
source = source_open(&lz_intf, lz, path);
if(!source) goto fail;
source->cap = SOURCE_WRITE;
return source;
fail:

View File

@ -87,7 +87,6 @@ static void lf_close(void *_cookie)
static source_intf_t lf_intf = {
.name = "Loaded File",
.cap = SOURCE_WRITE | SOURCE_RESIZE | SOURCE_SAVEAS,
.close_on_return_to_menu = false,
.read = lf_read,
.write = lf_write,
@ -126,6 +125,7 @@ source_t *source_loaded_file_open(char const *path)
source = source_open(&lf_intf, lf, path);
if(!source) goto fail;
source->cap = SOURCE_WRITE | SOURCE_RESIZE | SOURCE_SAVEAS;
close(fd);
return source;
@ -154,6 +154,7 @@ source_t *source_loaded_file_new(void)
if(!source) goto fail;
source->no_origin = true;
source->cap = SOURCE_WRITE | SOURCE_RESIZE | SOURCE_SAVEAS;
return source;
fail:

49
src/source-memory.c Normal file
View File

@ -0,0 +1,49 @@
#include "source.h"
#include <stdlib.h>
static memory_region_t REGION_ROM = {
.start = 0x80000000,
.end = 0x82000000,
.writable = false,
.name = "ROM",
};
static memory_region_t REGION_RAM = {
.start = 0x8c000000,
.end = 0x8c800000,
.writable = false,
.name = "RAM",
};
static memory_region_t REGION_ILRAM = {
.start = 0xe5200000,
.end = 0xe5201000,
.writable = true,
.name = "ILRAM",
};
static memory_region_t REGION_XYRAM = {
.start = 0xe500e000,
.end = 0xe5012000,
.writable = true,
.name = "XRAM/YRAM",
};
static memory_region_t REGION_RS = {
.start = 0xfd800000,
.end = 0xfd800800,
.writable = true,
.name = "RS",
};
memory_region_t *memory_all_regions[] = {
&REGION_ROM,
&REGION_RAM,
&REGION_ILRAM,
&REGION_XYRAM,
&REGION_RS,
NULL,
};
// TODO: Add offset to source + heditor view so we can see real memory addresses
source_t *source_memory_open(memory_region_t *region)
{
return NULL;
}

View File

@ -1 +1,22 @@
// TODO
// source-memory: A region of memory
//
// This data source is just a chunk of virtual memory. Be careful that memory
// is very volatile and the hex editor doesn't update automatically.
#pragma once
#include <stddef.h>
typedef struct {
/* Start and end addresses */
uint32_t start, end;
/* Whether that particular region is writable */
bool writable;
/* Region name */
char const *name;
} memory_region_t;
/* All memory regions (NULL terminated) */
extern memory_region_t *memory_all_regions[];
source_t *source_memory_open(memory_region_t *region);

View File

@ -24,8 +24,6 @@
typedef struct {
/* Interface name */
char const *name;
/* Source capabilities */
int cap;
/* Whether the source should be closed before we return to menu */
bool close_on_return_to_menu;
/* Load a chunk from the data source into a buffer. */
@ -53,6 +51,8 @@ typedef struct {
/* Interface identifier, and opaque interface data */
source_intf_t *intf;
void *cookie;
/* Source capabilities */
int cap;
/* Source name. Can be any string: file path, "memory at X", etc. */
char *origin;
/* If set, there is no origin and the "SAVE" button is disabled (only

View File

@ -76,3 +76,22 @@ char const *human_size(int size, char *str)
return str;
}
char const *human_isize(int size, char *str)
{
static char default_buffer[64];
if(str == NULL)
str = default_buffer;
char digits[16];
sprintf(digits, "%d", size);
if(size < (1 << 10)) /* 1 kiB */
sprintf(str, "%d B", size);
else if(size < (1 << 20)) /* 1 MiB */
sprintf(str, "%d kiB", size >> 10);
else
sprintf(str, "%d MiB", size >> 20);
return str;
}

View File

@ -13,3 +13,6 @@ bool confirm_discard(void);
/* Human version of file sizes. If str=NULL, returns a static buffer that is
overridden by further calls. */
char const *human_size(int size, char *str);
/* Human version of file sizes with SI units. */
char const *human_isize(int size, char *str);