diff --git a/include/draw.h b/include/draw.h index 4fb50c5..5e4870f 100644 --- a/include/draw.h +++ b/include/draw.h @@ -68,6 +68,7 @@ void jlDrawLine(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 jbyte jlDrawPixelGet(jlContextT *c, jint16 x, jint16 y); void jlDrawPixelSet(jlContextT *c, jint16 x, jint16 y); void jlPaletteDefault(jlContextT *c); +void jlPaletteGet(jlContextT *c, jbyte index, jbyte *r, jbyte *g, jbyte *b); // This is not a standard JoeyLib API. void jlPaletteSet(jlContextT *c, jbyte index, jbyte r, jbyte g, jbyte b); #define jlUtilStackPop(c, stack) _jlUtilStackPop(c, (jlStackT **)&(stack)) // Syntatic Sugar void *_jlUtilStackPop(jlContextT *c, jlStackT **stack); diff --git a/src/draw.c b/src/draw.c index 0b80f8c..b0f4498 100644 --- a/src/draw.c +++ b/src/draw.c @@ -414,6 +414,14 @@ void jlPaletteDefault(jlContextT *c) { } +void jlPaletteGet(jlContextT *c, jbyte index, jbyte *r, jbyte *g, jbyte *b) { + // This is not a standard JoeyLib API. It returns color values in 0-255 instead of 0-15. + *r = c->_jlPalette[index].r; + *g = c->_jlPalette[index].g; + *b = c->_jlPalette[index].b; +} + + void jlPaletteSet(jlContextT *c, jbyte index, jbyte r, jbyte g, jbyte b) { c->_jlPalette[index].r = r * 16; c->_jlPalette[index].g = g * 16; diff --git a/src/vector.c b/src/vector.c index e2fb431..eb43f9b 100644 --- a/src/vector.c +++ b/src/vector.c @@ -65,15 +65,18 @@ enum ClickStateE { }; typedef enum ClickStateE ClickStateT; + typedef struct VectorDataS { WindowDataT windowData; GtkWidget *drawVectorImage; GtkWidget *boxVectorForEditor; GtkWidget *editor; GtkWidget *fileVectorTraceImage; + GtkWidget *statusBar; ScintillaObject *sci; void *pLexer; int id; + int statusBarId; cairo_surface_t *surface; cairo_surface_t *scaled; cairo_surface_t *target; @@ -119,13 +122,17 @@ EVENT void menuVectorHelpVector(GtkWidget *object, gpointer userData); static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self); EVENT void scaleVectorTraceImageValueChanged(GtkWidget *object, gpointer userData); static void setDirty(VectorDataT *self, gboolean dirty); -EVENT void toolBoxClicked(GtkToolButton *object, gpointer userData); -EVENT void toolCircleClicked(GtkToolButton *object, gpointer userData); -EVENT void toolEllipseClicked(GtkToolButton *object, gpointer userData); -EVENT void toolFillClicked(GtkToolButton *object, gpointer userData); -EVENT void toolLineClicked(GtkToolButton *object, gpointer userData); -EVENT void toolPlotClicked(GtkToolButton *object, gpointer userData); -EVENT void toolRectangleClicked(GtkToolButton *object, gpointer userData); +static void sortCoordinates(int *x1, int *y1, int *x2, int *y2); +static void status(VectorDataT *self, char *message); +EVENT void toolBoxClicked(GtkToolButton *object, gpointer userData); +EVENT void toolCircleClicked(GtkToolButton *object, gpointer userData); +EVENT void toolColorClicked(GtkToolButton *object, gpointer userData); +EVENT void toolEllipseClicked(GtkToolButton *object, gpointer userData); +EVENT void toolFillClicked(GtkToolButton *object, gpointer userData); +EVENT void toolLineClicked(GtkToolButton *object, gpointer userData); +EVENT void toolPaletteClicked(GtkToolButton *object, gpointer userData); +EVENT void toolPlotClicked(GtkToolButton *object, gpointer userData); +EVENT void toolRectangleClicked(GtkToolButton *object, gpointer userData); static float variable(VectorDataT *self, unsigned char byte); static int word(VectorDataT *self, unsigned short word); EVENT gboolean winVectorClose(GtkWidget *object, gpointer userData); @@ -156,92 +163,112 @@ EVENT void drawVectorImageClick(GtkWidget *object, GdkEventButton *event, gpoint if (event->type == GDK_BUTTON_PRESS) { - // Clamp coordinates, just in case. - if (x < 0) x = 0; - if (x > 319) x = 319; - if (y < 0) y = 0; - if (y > 199) y = 199; - snprintf(temp, 8, "%d,%d", x, y); - - switch (self->clickState) { - case CLICK_NONE: - // Do nothing. - break; - - case CLICK_BOX_1: - insertText(self, temp); - insertText(self, " TO "); - self->clickState++; - break; - - case CLICK_BOX_2: - insertText(self, temp); + if (event->button == 2 || event->button == 3) { + if (self->clickState != CLICK_NONE) { + // Cancel any in-progress drawing action. self->clickState = CLICK_NONE; - break; + status(self, "Tool canceled."); + } + } - case CLICK_CIRCLE_1: - self->clickTempPoint.x = x; - self->clickTempPoint.y = y; - insertText(self, temp); - insertText(self, " RADIUS "); - self->clickState++; - break; + if (event->button == 1) { + // Clamp coordinates, just in case. + if (x < 0) x = 0; + if (x > 319) x = 319; + if (y < 0) y = 0; + if (y > 199) y = 199; + snprintf(temp, 8, "%d,%d", x, y); - case CLICK_CIRCLE_2: - x = sqrt(pow(x - self->clickTempPoint.x, 2) + pow(y - self->clickTempPoint.y, 2)); - snprintf(temp, 8, "%d", x); - insertText(self, temp); - self->clickState = CLICK_NONE; - break; + switch (self->clickState) { + case CLICK_NONE: + // Do nothing. + break; - case CLICK_ELLIPSE_1: - insertText(self, temp); - insertText(self, " TO "); - self->clickState++; - break; + case CLICK_BOX_1: + insertText(self, temp); + insertText(self, " to "); + self->clickState++; + status(self, "Box. Select opposite corner."); + break; - case CLICK_ELLIPSE_2: - insertText(self, temp); - self->clickState = CLICK_NONE; - break; + case CLICK_BOX_2: + insertText(self, temp); + self->clickState = CLICK_NONE; + status(self, ""); + break; - case CLICK_FILL_1: - insertText(self, temp); - self->clickState = CLICK_NONE; - break; + case CLICK_CIRCLE_1: + self->clickTempPoint.x = x; + self->clickTempPoint.y = y; + insertText(self, temp); + insertText(self, " radius "); + self->clickState++; + status(self, "Circle. Select radius."); + break; - case CLICK_LINE_1: - insertText(self, temp); - insertText(self, " TO "); - self->clickState++; - break; + case CLICK_CIRCLE_2: + x = sqrt(pow(x - self->clickTempPoint.x, 2) + pow(y - self->clickTempPoint.y, 2)); + snprintf(temp, 8, "%d", x); + insertText(self, temp); + self->clickState = CLICK_NONE; + status(self, ""); + break; - case CLICK_LINE_2: - insertText(self, temp); - self->clickState++; - break; + case CLICK_ELLIPSE_1: + insertText(self, temp); + insertText(self, " to "); + self->clickState++; + status(self, "Ellipse. Select opposite corner."); + break; - case CLICK_LINE_X: - insertText(self, " TO "); - insertText(self, temp); - break; + case CLICK_ELLIPSE_2: + insertText(self, temp); + self->clickState = CLICK_NONE; + break; - case CLICK_PLOT_1: - insertText(self, temp); - insertText(self, " TO "); - self->clickState = CLICK_NONE; - break; + case CLICK_FILL_1: + insertText(self, temp); + self->clickState = CLICK_NONE; + status(self, ""); + break; - case CLICK_RECTANGLE_1: - insertText(self, temp); - insertText(self, " TO "); - self->clickState++; - break; + case CLICK_LINE_1: + insertText(self, temp); + insertText(self, " to "); + self->clickState++; + status(self, "Line. Select next point."); + break; - case CLICK_RECTANGLE_2: - insertText(self, temp); - self->clickState = CLICK_NONE; - break; + case CLICK_LINE_2: + insertText(self, temp); + self->clickState++; + status(self, "Line. Select additional points, right-click to finish."); + break; + + case CLICK_LINE_X: + insertText(self, " to "); + insertText(self, temp); + break; + + case CLICK_PLOT_1: + insertText(self, temp); + self->clickState = CLICK_NONE; + status(self, ""); + break; + + case CLICK_RECTANGLE_1: + insertText(self, temp); + insertText(self, " to "); + self->clickState++; + status(self, "Rectangle. Select opposite corner."); + break; + + case CLICK_RECTANGLE_2: + insertText(self, temp); + self->clickState = CLICK_NONE; + status(self, ""); + break; + } } } @@ -377,8 +404,6 @@ EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotifi switch (notifyData->nmhdr.code) { case SCN_MODIFIED: if (notifyData->modificationType & SC_MOD_INSERTTEXT || notifyData->modificationType & SC_MOD_DELETETEXT) { - // Cancel any in-progress drawing action. - self->clickState = CLICK_NONE; // Is it safe to repaint now? if (self->allowRedraw) { // Allocate space to fetch code from editor. @@ -430,11 +455,12 @@ EVENT void fileVectorTraceImageFileSet(GtkWidget *object, gpointer userData) { VectorDataT *self = (VectorDataT *)userData; char *temp = NULL; - debug("fileVectorTraceImageFileSet fired\n"); temp = (char *)gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(object)); loadTraceImage(self, temp); DEL(temp); + status(self, "Trace image loaded."); + setDirty(self, TRUE); } @@ -455,7 +481,6 @@ static void insertCommand(VectorDataT *self, char *command) { int pos; int line; int len; - char *newLine = "\n\0"; // Position Scintilla cursor at the start of the current line. pos = SSM(SCI_GETCURRENTPOS, 0, 0); @@ -466,7 +491,7 @@ static void insertCommand(VectorDataT *self, char *command) { self->allowRedraw = FALSE; // Collect leading whitespace on this line. - len = SSM(SCI_GETLINE, line, NULL); + len = SSM(SCI_GETLINE, line, (sptr_t)NULL); utilEnsureBufferSize((unsigned char **)&self->buffer, &self->bufferLength, len + 1); SSM(SCI_GETLINE, line, (sptr_t)self->buffer); self->buffer[len + 1] = 0; @@ -615,6 +640,7 @@ EVENT void menuVectorFileNew(GtkWidget *object, gpointer userData) { if (!utilQuestionDialog(self->windowData.window, "New", "You have unsaved changes. Start new?")) { return; } + status(self, "New image."); } // Clear editor. @@ -704,6 +730,7 @@ EVENT void menuVectorFileOpen(GtkWidget *object, gpointer userData) { if (line != NULL) DEL(line); SSM(SCI_ADDTEXT, strlen(self->buffer), (sptr_t)self->buffer); //SSM(SCI_CONVERTEOLS, SC_EOL_CR, 0); + status(self, "Image loaded."); setDirty(self, FALSE); // Do again - loading text marks us dirty. } else { //***TODO*** Something bad happened. @@ -749,6 +776,7 @@ EVENT void menuVectorFileSave(GtkWidget *object, gpointer userData) { fclose(out); // Release code. DEL(code); + status(self, "Saved."); // We're clean now. setDirty(self, FALSE); } else { @@ -839,6 +867,7 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) { y1 = word(self, GET_WORD); x2 = word(self, GET_WORD); y2 = word(self, GET_WORD); + sortCoordinates(&x1, &y1, &x2, &y2); debug("Box %d,%d to %d,%d\n", x1, y1, x2, y2); jlDrawBoxFilled(self->jlc, x1, y1, x2, y2); ADD_POINT(x1, y1); @@ -881,6 +910,7 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) { y1 = word(self, GET_WORD); x2 = word(self, GET_WORD); y2 = word(self, GET_WORD); + sortCoordinates(&x1, &y1, &x2, &y2); debug("Ellipse %d,%d to %d,%d\n", x1, y1, x2, y2); jlDrawEllipse(self->jlc, x1, y1, x2, y2); ADD_POINT(x1, y1); @@ -1083,6 +1113,7 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) { y1 = word(self, GET_WORD); x2 = word(self, GET_WORD); y2 = word(self, GET_WORD); + sortCoordinates(&x1, &y1, &x2, &y2); debug("Rectangle %d,%d to %d,%d\n", x1, y1, x2, y2); jlDrawBox(self->jlc, x1, y1, x2, y2); ADD_POINT(x1, y1); @@ -1161,59 +1192,156 @@ static void setDirty(VectorDataT *self, gboolean dirty) { } +static void sortCoordinates(int *x1, int *y1, int *x2, int *y2) { + int temp; + + if (*x2 < *x1) { + temp = *x1; + *x1 = *x2; + *x2 = temp; + } + + if (*y2 < *y1) { + temp = *y1; + *y1 = *y2; + *y2 = temp; + } +} + + +static void status(VectorDataT *self, char *message) { + gtk_statusbar_remove_all(GTK_STATUSBAR(self->statusBar), self->statusBarId); + gtk_statusbar_push(GTK_STATUSBAR(self->statusBar), self->statusBarId, message); +} + + EVENT void toolBoxClicked(GtkToolButton *object, gpointer userData) { VectorDataT *self = (VectorDataT *)userData; - insertCommand(self, "BOX"); + (void)object; + + insertCommand(self, "box"); self->clickState = CLICK_BOX_1; + status(self, "Box. Select first corner."); } EVENT void toolCircleClicked(GtkToolButton *object, gpointer userData) { VectorDataT *self = (VectorDataT *)userData; - insertCommand(self, "CIRCLE"); + (void)object; + + insertCommand(self, "circle"); self->clickState = CLICK_CIRCLE_1; + status(self, "Circle. Select center."); +} + + +EVENT void toolColorClicked(GtkToolButton *object, gpointer userData) { + VectorDataT *self = (VectorDataT *)userData; + GtkWidget *dialog; + GdkRGBA colors[16]; + GdkRGBA selected; + char temp[4]; + int i; + unsigned char r; + unsigned char g; + unsigned char b; + double scale = 1.0/256.0; + + (void)object; + + // Load the current JoeyLib palette into the dialog. + for (i=0; i<16; i++) { + jlPaletteGet(self->jlc, i, &r, &g, &b); + colors[i].alpha = 1.0; + colors[i].red = (double)r * scale; + colors[i].green = (double)g * scale; + colors[i].blue = (double)b * scale; + } + + // Build the dialog. + dialog = gtk_color_chooser_dialog_new("Color", GTK_WINDOW(self->windowData.window)); + g_object_set (dialog, "show-editor", FALSE, NULL); + gtk_color_chooser_add_palette(GTK_COLOR_CHOOSER(dialog), GTK_ORIENTATION_HORIZONTAL, 4, 16, colors); + + // Run the dialog. + if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_CANCEL) { + // Figure out which color index they selected. + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &selected); + for (i=0; i<16; i++) { + if (colors[i].red == selected.red && colors[i].green == selected.green && colors[i].blue == selected.blue) { + // Write our selected color into the editor. + snprintf(temp, 4, " %d", i); + insertCommand(self, "COLOR"); + insertText(self, temp); + } + } + } + + gtk_widget_destroy(dialog); } EVENT void toolEllipseClicked(GtkToolButton *object, gpointer userData) { VectorDataT *self = (VectorDataT *)userData; - insertCommand(self, "ELLIPSE"); + (void)object; + + insertCommand(self, "ellipse"); self->clickState = CLICK_ELLIPSE_1; + status(self, "Ellipse. Select first corner."); } EVENT void toolFillClicked(GtkToolButton *object, gpointer userData) { VectorDataT *self = (VectorDataT *)userData; - insertCommand(self, "FILL"); + (void)object; + + insertCommand(self, "fill"); self->clickState = CLICK_FILL_1; + status(self, "Fill. Select fill location."); } EVENT void toolLineClicked(GtkToolButton *object, gpointer userData) { VectorDataT *self = (VectorDataT *)userData; - insertCommand(self, "LINE"); + (void)object; + + insertCommand(self, "line"); self->clickState = CLICK_LINE_1; + status(self, "Line. Select first point."); +} + + +EVENT void toolPaletteClicked(GtkToolButton *object, gpointer userData) { + VectorDataT *self = (VectorDataT *)userData; + + (void)object; } EVENT void toolPlotClicked(GtkToolButton *object, gpointer userData) { VectorDataT *self = (VectorDataT *)userData; - insertCommand(self, "PLOT"); + (void)object; + + insertCommand(self, "plot"); self->clickState = CLICK_PLOT_1; + status(self, "Plot. Select location."); } EVENT void toolRectangleClicked(GtkToolButton *object, gpointer userData) { VectorDataT *self = (VectorDataT *)userData; - insertCommand(self, "RECTANGLE"); + (void)object; + + insertCommand(self, "rectangle"); self->clickState = CLICK_RECTANGLE_1; + status(self, "Rectangle. Select first corner."); } @@ -1280,12 +1408,14 @@ void winVectorCreate(void) { "boxVectorForEditor", "drawVectorImage", "fileVectorTraceImage", + "status", NULL }; GtkWidget **widgets[] = { NULL, NULL, NULL, + NULL, NULL }; @@ -1302,18 +1432,21 @@ void winVectorCreate(void) { widgets[1] = &self->boxVectorForEditor; widgets[2] = &self->drawVectorImage; widgets[3] = &self->fileVectorTraceImage; + widgets[4] = &self->statusBar; utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/Vector.glade", widgetNames, widgets, self); // Grab title. self->title = strdup(gtk_window_get_title(GTK_WINDOW(self->windowData.window))); + // Get status bar context ID. + self->statusBarId = gtk_statusbar_get_context_id(GTK_STATUSBAR(self->statusBar), "JoeyDev"); + // Add our custom icons. gtk_icon_theme_add_resource_path(gtk_icon_theme_get_for_screen(gdk_screen_get_default()), "/com/kangaroopunch/joeydev/icons"); // Add missing event to drawVectorImage - gtk_widget_add_events(self->drawVectorImage, GDK_BUTTON_PRESS_MASK); + gtk_widget_add_events(self->drawVectorImage, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK); g_signal_connect(G_OBJECT(self->drawVectorImage), "button-press-event", G_CALLBACK(drawVectorImageClick), self); - gtk_widget_add_events(self->drawVectorImage, GDK_POINTER_MOTION_MASK); // Create Scintilla editor. self->editor = scintilla_new(); @@ -1401,9 +1534,12 @@ void winVectorCreate(void) { cairo_surface_flush(self->surface); self->jlc = jlContextNew(cairo_image_surface_get_data(self->surface)); self->traceImagePercent = 50 * 0.01; + // Register window & show it. utilWindowRegister(self); gtk_widget_show_all(self->windowData.window); + + status(self, "Welcome to the Victor Vector Editor!"); } diff --git a/ui/Vector.glade b/ui/Vector.glade index 361070b..9166cac 100644 --- a/ui/Vector.glade +++ b/ui/Vector.glade @@ -1,5 +1,5 @@ -