Merge remote-tracking branch 'origin/emulator' into dev_aaron

This commit is contained in:
id101010
2015-06-01 18:51:27 +02:00
47 changed files with 2503 additions and 844 deletions

241
common/app/pixy_helper.c Normal file
View File

@@ -0,0 +1,241 @@
#include "pixy_helper.h"
#include "pixy.h"
#include "tft.h"
#include <stdlib.h>
static int renderBA81(uint16_t xpos, uint16_t ypos, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame);
static int saveBA81(FILE_HANDLE* handle, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame);
int pixy_render_full_frame(uint16_t x, uint16_t y) {
return pixy_render_cropped_frame(x,y,0,0,320,200);
}
int pixy_render_cropped_frame(uint16_t x, uint16_t y, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height) {
uint8_t* videodata;
int32_t response;
int32_t fourccc;
int8_t renderflags;
uint16_t xwidth;
uint16_t ywidth;
uint32_t size;
int return_value = pixy_command("cam_getFrame", // String id for remote procedure
INT8(0x21), // mode
INT16(xoffset), // xoffset
INT16(yoffset), // yoffset
INT16(width), // width
INT16(height), // height
END_OUT_ARGS, // separator
&response, // pointer to mem address for return value
&fourccc,
&renderflags,
&xwidth,
&ywidth,
&size,
&videodata, // pointer to mem address for returned frame
END_IN_ARGS);
if(return_value==0) {
return_value = renderBA81(x,y,xwidth,ywidth,size,videodata);
}
return return_value;
}
int pixy_save_full_frame(FILE_HANDLE* handle) {
return pixy_save_cropped_frame(handle,0,0,320,200);
}
int pixy_save_cropped_frame(FILE_HANDLE* handle, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height) {
uint8_t* videodata;
int32_t response;
int32_t fourccc;
int8_t renderflags;
uint16_t xwidth;
uint16_t ywidth;
uint32_t size;
int return_value = pixy_command("cam_getFrame", // String id for remote procedure
INT8(0x21), // mode
INT16(xoffset), // xoffset
INT16(yoffset), // yoffset
INT16(width), // width
INT16(height), // height
END_OUT_ARGS, // separator
&response, // pointer to mem address for return value
&fourccc,
&renderflags,
&xwidth,
&ywidth,
&size,
&videodata, // pointer to mem address for returned frame
END_IN_ARGS);
if(return_value==0) {
return_value = saveBA81(handle,xwidth,ywidth,size,videodata);
}
return return_value;
}
static void interpolateBayer(uint16_t width, uint16_t x, uint16_t y, uint8_t *pixel, uint8_t* r, uint8_t* g, uint8_t* b)
{
if (y&1)
{
if (x&1)
{
*r = *pixel;
*g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2;
*b = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2;
}
else
{
*r = (*(pixel-1)+*(pixel+1))>>1;
*g = *pixel;
*b = (*(pixel-width)+*(pixel+width))>>1;
}
}
else
{
if (x&1)
{
*r = (*(pixel-width)+*(pixel+width))>>1;
*g = *pixel;
*b = (*(pixel-1)+*(pixel+1))>>1;
}
else
{
*r = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2;
*g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2;
*b = *pixel;
}
}
}
static int renderBA81(uint16_t xpos, uint16_t ypos, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame)
{
uint16_t x, y;
uint8_t r, g, b;
// skip first line
frame += width;
// don't render top and bottom rows, and left and rightmost columns because of color
// interpolation
//uint32_t decodedimage[(width-2)*(height-2)];
uint16_t* decodedimage = malloc(sizeof(uint16_t)*(width-2)*(height-2));
if(decodedimage==NULL) { //not enough free space to decode image in memory
//decode & render image pixel by pixel
for (y=1; y<height-1; y++)
{
frame++;
for (x=1; x<width-1; x++, frame++)
{
interpolateBayer(width, x, y, frame, &r, &g, &b);
tft_draw_pixel(xpos+x-1,ypos+y-1,RGB(r,g,b));
}
frame++;
}
} else { //enough space
uint16_t* line = decodedimage;
for (y=1; y<height-1; y++)
{
//line = (unsigned int *)img.scanLine(y-1);
frame++;
for (x=1; x<width-1; x++, frame++)
{
interpolateBayer(width, x, y, frame, &r, &g, &b);
//*line++ = (0xff<<24) | (r<<16) | (g<<8) | (b<<0);
*line++ = RGB(r,g,b);
}
frame++;
}
tft_draw_bitmap_unscaled(xpos,ypos,width-2,height-2,decodedimage);
free(decodedimage);
}
return 0;
}
static int saveBA81(FILE_HANDLE* handle, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame)
{
uint16_t x, y;
uint8_t r, g, b;
uint32_t fpos = handle->fpos;
uint32_t row_size_padded = ((width-2)*3 + 3) & (~3); //row size aligned to 4 bytes
uint32_t fpos_end = fpos + row_size_padded* (height-2);
// skip first line
frame += width;
// don't render top and bottom rows, and left and rightmost columns because of color
// interpolation
for (y=1; y<height-1; y++)
{
frame++;
uint8_t rowbuf[row_size_padded];
//Bitmaps are saved "bottom-up". Seek to the right row.
if(filesystem_file_seek(handle,fpos_end-row_size_padded*y)!=F_OK) {
return -1;
}
for (x=1; x<width-1; x++, frame++)
{
interpolateBayer(width, x, y, frame, &r, &g, &b);
//bitmaps are saved in 24bit b,g,r format
rowbuf[(x-1)*3] = b;
rowbuf[(x-1)*3+1] = g;
rowbuf[(x-1)*3+2] = r;
}
if(filesystem_file_write(handle,rowbuf,row_size_padded)!=F_OK) {
return -1;
}
frame++;
}
return 0;
}
int pixy_cc_set_region(uint8_t signum, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height) {
int32_t response;
int return_value = pixy_command("cc_setSigRegion", // String id for remote procedure
INT32(0), // type = normal color code
INT8(signum),
INT16(xoffset), // xoffset
INT16(yoffset), // yoffset
INT16(width), // width
INT16(height), // height
END_OUT_ARGS, // separator
&response, // pointer to mem address for return value
END_IN_ARGS);
return return_value;
}

59
common/app/pixy_helper.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef PIXY_HELPER_H
#define PIXY_HELPER_H
#include <stdbool.h>
#include <stdint.h>
#include "filesystem.h"
/**
* Receives a fullsized frame from pixy and display's it on the display with the topleft corner at (x,y)
* @param x The x-Coordinate of the top left corner
* @param y The y-Coordinate of the top left corner
* @return 0 on success, otherwise the errorcode from pixy
*/
int pixy_render_full_frame(uint16_t x, uint16_t y);
/**
* Receives a cropped frame from pixy and display's it on the display with the topleft corner at (x,y)
* @param x The x-Coordinate of the top left corner to draw the image
* @param y The y-Coordinate of the top left corner to draw the image
* @param xoffset The x-Coordinate on the pixy image from where on you want the frame data
* @param yoffset The y-Coordinate on the pixy image from where on you want the frame data
* @param width The width of the image recorded from pixy
* @param height The height of the image recorded from pixy
* @return 0 on success, otherwise the errorcode from pixy
*/
int pixy_render_cropped_frame(uint16_t x, uint16_t y, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height);
/**
* Receives a fullsized frame from pixy and saves it to the given file in the 24bit (b,g,a) format.
* Use this method to write the bitmap-data part of a windows bitmap (.bmp).
* This method will neither open nor close the passed file.
* @param handle The file to write the data to. The file must be open and it should be seeked to the right position.
* @return 0 on success, otherwise the errorcode from pixy
*/
int pixy_save_full_frame(FILE_HANDLE* handle);
/**
* Receives a cropped frame from pixy and saves it to the given file in the 24bit (b,g,a) format.
* @param handle The file to write the data to. The file must be open and it should be seeked to the right position.
* @param xoffset The x-Coordinate on the pixy image from where on you want the frame data
* @param yoffset The y-Coordinate on the pixy image from where on you want the frame data
* @param width The width of the image recorded from pixy
* @param height The height of the image recorded from pixy
* @return 0 on success, otherwise the errorcode from pixy
*/
int pixy_save_cropped_frame(FILE_HANDLE* handle, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height);
/**
* Sets the color signature to the color in the selected region of the frame
* @param signum the color signature number (1..7)
* @param xoffset The x-Coordinate of the topleft point of the region
* @param yoffset The y-Coordinate of the topleft point of the region
* @param width The width of the region
* @param height The height of the region
* @return 0 on success, otherwise the errorcode from pixy
*/
int pixy_cc_set_region(uint8_t signum, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height);
#endif /* PIXY_HELPER_H */

View File

@@ -70,6 +70,7 @@ static void enter(void* screen) {
y+=14;
}
filesystem_dir_close(dir);
y+=14;
@@ -129,46 +130,10 @@ SCREEN_STRUCT* get_screen_filetest() {
}
static void image_test() {
//Source: http://stackoverflow.com/a/17040962/2606757
FILE_HANDLE* file = filesystem_file_open("cpu.bmp");
if(file==NULL) {
if(!tft_draw_bitmap_file_unscaled(250,170,"cpu.bmp")) {
tft_print_line(10,180,BLUE,TRANSPARENT,0,"Could not open cpu.bmp");
return;
}
unsigned char info[54];
if(filesystem_file_read(file,info,54)!=F_OK) {
tft_print_line(10,180,BLUE,TRANSPARENT,0,"Could not read header of cpu.bmp");
filesystem_file_close(file);
return;
}
// extract image height and width from header
int width = *(int*)&info[18];
int height = *(int*)&info[22];
filesystem_file_seek(file,*(int*)&info[10]);
unsigned char data [width*4];
tft_draw_rectangle(100-1,160-1,100-1+width,160-1+height,BLACK);
for(int i = 0; i < height; i++)
{
filesystem_file_read(file,data,width*4);
for(int j = 0; j < width*4; j += 4)
{
unsigned char a = data[j];
unsigned char r = data[j+1];
unsigned char g = data[j+2];
unsigned char b = data[j+3];
if(a!=0) {
tft_draw_pixel(100+j/4,160+height-1-i,RGB(r,g,b));
}
}
}
filesystem_file_close(file);
tft_draw_rectangle(250-1,170-1,250-1+64,170-1+64,BLACK);
}

View File

@@ -6,7 +6,7 @@
/*@{*/
/**
* @defgroup filetest Filetest
* @defgroup filetest Filetest (Screen)
* The File-Test Screen tests the filesystem module. It read/writes from/to files and shows a bitmap
*/
/*@{*/
@@ -19,4 +19,5 @@
*/
SCREEN_STRUCT* get_screen_filetest();
/*@}@}*/
/*@}*/
/*@}*/

View File

@@ -7,7 +7,7 @@
/*@{*/
/**
* @defgroup guitest Guitest
* @defgroup guitest Guitest (Screen)
* The Gui-Test Screen tests the gui and the tft module.
*/
/*@{*/
@@ -20,4 +20,5 @@
*/
SCREEN_STRUCT* get_screen_guitest();
/*@}@}*/
/*@}*/
/*@}*/

View File

@@ -2,13 +2,35 @@
#include "screen_guitest.h"
#include "screen_pixytest.h"
#include "screen_filetest.h"
#include "screen_photomode.h"
#include "screen_tracking.h"
#include "button.h"
#include "tft.h"
#include "filesystem.h"
BUTTON_STRUCT b_guitest;
BUTTON_STRUCT b_pixytest;
BUTTON_STRUCT b_filetest;
BUTTON_STRUCT b_our_tracking;
BUTTON_STRUCT b_ref_tracking;
BUTTON_STRUCT b_photo_mode;
static void b_our_tracking_cb(void* button) {
tracking_set_mode(OUR_TRACKING);
gui_screen_navigate(get_screen_tracking());
}
static void b_ref_tracking_cb(void* button) {
tracking_set_mode(REFERENCE_TRACKING);
gui_screen_navigate(get_screen_tracking());
}
static void b_photo_mode_cb(void* button) {
gui_screen_navigate(get_screen_photomode());
}
static void b_guitest_cb(void* button) {
gui_screen_navigate(get_screen_guitest());
}
@@ -25,47 +47,108 @@ static void b_pixytest_cb(void* button) {
static void enter(void* screen) {
tft_clear(WHITE);
//button to reach guitest
b_guitest.base.x1=25; //Start X of Button
b_guitest.base.y1=45; //Start Y of Button
b_guitest.base.x2=AUTO; //b_guitest.base.x1+160; //Auto Calculate X2 with String Width
b_guitest.base.y2=AUTO; //Auto Calculate Y2 with String Height
b_guitest.txtcolor=WHITE; //Set foreground color
b_guitest.bgcolor=HEX(0xDE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
b_guitest.font=0; //Select Font
b_guitest.text="Gui/Tft Test"; //Set Text (For formatted strings take sprintf)
b_guitest.callback=b_guitest_cb; //Call b_guitest_cb as Callback
gui_button_add(&b_guitest); //Register Button (and run the callback from now on)
//Heading
tft_print_line(10,10,BLUE,TRANSPARENT,1,"Discoverpixy");
tft_draw_line(0,40,319,40,BLACK);
//button to reach pixy test
b_pixytest.base.x1=150; //Start X of Button
b_pixytest.base.y1=45; //Start Y of Button
b_pixytest.base.x2=AUTO; //b_pixytest.base.x1+160; //Auto Calculate X2 with String Width
b_pixytest.base.y2=AUTO; //Auto Calculate Y2 with String Height
b_pixytest.txtcolor=BLUE; //Set foreground color
b_pixytest.bgcolor=HEX(0x10DE10); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
b_pixytest.font=0; //Select Font
b_pixytest.text="Pixy Test"; //Set Text (For formatted strings take sprintf)
b_pixytest.callback=b_pixytest_cb; //Call b_pixytest_cb as Callback
gui_button_add(&b_pixytest); //Register Button (and run the callback from now on)
#define X_TAB 90
#define BUTTON_SPACING 7
//button to reach filesystem test
b_filetest.base.x1=240; //Start X of Button
b_filetest.base.y1=45; //Start Y of Button
b_filetest.base.x2=AUTO; //b_filetest.base.x1+160; //Auto Calculate X2 with String Width
b_filetest.base.y2=AUTO; //Auto Calculate Y2 with String Height
b_filetest.txtcolor=WHITE; //Set foreground color
b_filetest.bgcolor=HEX(0x501EA0); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
b_filetest.font=0; //Select Font
b_filetest.text="File Test"; //Set Text (For formatted strings take sprintf)
b_filetest.callback=b_filetest_cb; //Call b_filetest_cb as Callback
gui_button_add(&b_filetest); //Register Button (and run the callback from now on)
//First line of buttons
#define Y_FIRST 60
tft_print_line(10,Y_FIRST,BLACK,TRANSPARENT,0,"Tracking:");
b_our_tracking.base.x1=X_TAB; //Start X of Button
b_our_tracking.base.y1=Y_FIRST-3; //Start Y of Button
b_our_tracking.base.x2=AUTO; //Auto Calculate X2 with String Width
b_our_tracking.base.y2=AUTO; //Auto Calculate Y2 with String Height
b_our_tracking.txtcolor=WHITE; //Set foreground color
b_our_tracking.bgcolor=HEX(0xE30535); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
b_our_tracking.font=0; //Select Font
b_our_tracking.text="Our Tracking"; //Set Text (For formatted strings take sprintf)
b_our_tracking.callback=b_our_tracking_cb; //Call b_our_tracking when the button get's pressed
gui_button_add(&b_our_tracking); //Register Button (and run the callback from now on)
b_ref_tracking.base.x1=b_our_tracking.base.x2+BUTTON_SPACING;
b_ref_tracking.base.y1=Y_FIRST-3;
b_ref_tracking.base.x2=AUTO;
b_ref_tracking.base.y2=AUTO;
b_ref_tracking.txtcolor=WHITE;
b_ref_tracking.bgcolor=HEX(0xFF2151);
b_ref_tracking.font=0;
b_ref_tracking.text="Reference Tracking";
b_ref_tracking.callback=b_ref_tracking_cb;
gui_button_add(&b_ref_tracking);
//Second line of buttons
#define Y_SECOND Y_FIRST+25
tft_print_line(10,Y_SECOND,BLACK,TRANSPARENT,0,"Photo mode:");
b_photo_mode.base.x1=X_TAB;
b_photo_mode.base.y1=Y_SECOND-3;
b_photo_mode.base.x2=AUTO;
b_photo_mode.base.y2=AUTO;
b_photo_mode.txtcolor=WHITE;
b_photo_mode.bgcolor=HEX(0x21B1FF);
b_photo_mode.font=0;
b_photo_mode.text="Photo Mode";
b_photo_mode.callback=b_photo_mode_cb;
gui_button_add(&b_photo_mode);
//Third line of buttons
#define Y_THIRD Y_SECOND+25
tft_print_line(10,Y_THIRD,BLACK,TRANSPARENT,0,"Tests:");
b_guitest.base.x1=X_TAB;
b_guitest.base.y1=Y_THIRD-3;
b_guitest.base.x2=AUTO;
b_guitest.base.y2=AUTO;
b_guitest.txtcolor=BLACK;
b_guitest.bgcolor=HEX(0x00FA21);
b_guitest.font=0;
b_guitest.text="Gui Test";
b_guitest.callback=b_guitest_cb;
gui_button_add(&b_guitest);
b_pixytest.base.x1=b_guitest.base.x2+BUTTON_SPACING;
b_pixytest.base.y1=Y_THIRD-3;
b_pixytest.base.x2=AUTO;
b_pixytest.base.y2=AUTO;
b_pixytest.txtcolor=BLACK;
b_pixytest.bgcolor=HEX(0x00FA96);
b_pixytest.font=0;
b_pixytest.text="Pixy Test";
b_pixytest.callback=b_pixytest_cb;
gui_button_add(&b_pixytest);
b_filetest.base.x1=b_pixytest.base.x2+BUTTON_SPACING;
b_filetest.base.y1=Y_THIRD-3;
b_filetest.base.x2=AUTO;
b_filetest.base.y2=AUTO;
b_filetest.txtcolor=BLACK;
b_filetest.bgcolor=HEX(0x00FAC4);
b_filetest.font=0;
b_filetest.text="File Test";
b_filetest.callback=b_filetest_cb;
gui_button_add(&b_filetest);
//Bottom line
tft_draw_line(0,145,319,145,BLACK);
tft_print_line(10,150,BLUE,TRANSPARENT,0,"Powered by");
tft_draw_bitmap_file_unscaled(10,165,"pixy_small.bmp");
tft_draw_bitmap_file_unscaled(165,165,"stm_small.bmp");
}
static void leave(void* screen) {
gui_button_remove(&b_our_tracking);
gui_button_remove(&b_ref_tracking);
gui_button_remove(&b_photo_mode);
gui_button_remove(&b_guitest);
gui_button_remove(&b_pixytest);
gui_button_remove(&b_filetest);

View File

@@ -18,7 +18,7 @@
/*@{*/
/**
* @defgroup main Main
* @defgroup main Main (Screen)
* The Main Screen is the start-screen for the application
*/
/*@{*/
@@ -31,4 +31,5 @@
*/
SCREEN_STRUCT* get_screen_main();
/*@}@}*/
/*@}*/
/*@}*/

View File

@@ -0,0 +1,166 @@
#include "screen_photomode.h"
#include "screen_photomode_save.h"
#include "button.h"
#include "tft.h"
#include "touch.h"
#include "pixy.h"
#include "system.h"
#include "pixy_helper.h"
static bool pixy_connected = false; //Whether or not the pixy cam is currently connected
static BUTTON_STRUCT b_back; //Button to navigate back
static BUTTON_STRUCT b_save; //Button to save the current image
static TOUCH_AREA_STRUCT a_area; //Touch Area, where the frame is drawn. Used to drag the image around
static bool subMenu=false; //Whether or not we left the current screen for a submenu
//Callback for when the user presses the "back" button
static void b_back_cb(void* button) {
subMenu = false; //we're not entering a submenu
gui_screen_back(); //navigate back to the previous screen
}
//Callback for when the user presses the "save" button
static void b_save_cb(void* button) {
subMenu = true; //we're entering a submenu
gui_screen_navigate(get_screen_photomodesave()); //navigate to the save screen
}
static POINT_STRUCT pixy_pos; //The current position of pixy's servos
static POINT_STRUCT old_pos; //The last touch position on the screen
//Callback for when the user drags the image around
static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction) {
POINT_STRUCT p = touch_get_last_point(); //get the last touched point
switch(triggeredAction) {
case PEN_ENTER:
case PEN_DOWN:
old_pos = p; //If the user "newly" enters the touch area, we set the "last" position to the current
break;
case PEN_MOVE: //the user is moving around, he entered the screen a while ago (old_pos is set)
{
int16_t deltaX = p.x - old_pos.x; //Calculate x difference between last and current touch
int16_t deltaY = p.y - old_pos.y; //Calculate y difference between last and current touch
old_pos=p; //store the current touch point for the next time
//printf("%d %d\n",deltaX,deltaY);
if(pixy_connected) {
//Calculate new servo coordinates. 2 is just a proportional factor
int16_t new_x = pixy_pos.x+deltaX*2;
int16_t new_y = pixy_pos.y-deltaY*2;
//check limits
if(new_x<0) new_x=0;
if(new_x>1000) new_x=1000;
if(new_y<0) new_y=0;
if(new_y>1000) new_y=1000;
//set pixy_pos so that the main routine can send it to the servos
pixy_pos.x = new_x;
pixy_pos.y= new_y;
}
}
break;
case PEN_UP:
case PEN_LEAVE:
//printf("Leave/up\n");
break;
default: break;
}
}
//Callback for when the screen is entered/loaded
static void enter(void* screen) {
tft_clear(WHITE);
tft_print_line(5,5,BLACK,TRANSPARENT,0,"Drag the image around and ");
//Back button
b_back.base.x1=5; //Start X of Button
b_back.base.y1=19; //Start Y of Button
b_back.base.x2=AUTO; //Auto Calculate X2 with String Width
b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height
b_back.txtcolor=WHITE; //Set foreground color
b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
b_back.font=0; //Select Font
b_back.text="Back"; //Set Text (For formatted strings take sprintf)
b_back.callback=b_back_cb; //Call b_back_cb as Callback
gui_button_add(&b_back); //Register Button (and run the callback from now on)
//Save button
b_save.base.x1=190;
b_save.base.y1=3;
b_save.base.x2=AUTO;
b_save.base.y2=AUTO;
b_save.txtcolor=WHITE;
b_save.bgcolor=HEX(0x1010AE);
b_save.font=0;
b_save.text="Save it!";
b_save.callback=b_save_cb;
gui_button_add(&b_save);
//Frame Coordinates: topleft = (1,40); bottomright = (318,238)
//Leave a 10px border for the area
//Area to drag the image around
a_area.hookedActions = PEN_DOWN | PEN_MOVE | PEN_ENTER | PEN_UP | PEN_LEAVE;
a_area.x1 = 11;
a_area.y1 = 50;
a_area.x2 = 308;
a_area.y2 = 228;
a_area.callback = touchCB;
touch_register_area(&a_area);
//Pixy stuff
pixy_connected = (pixy_init()==0); //try to connect to pixy
if(pixy_connected && !subMenu) { //pixy is connected, but we are not coming from a submenu
pixy_pos.x=pixy_pos.y=500; //reset servo positions to center
}
}
//Callback for when the screen is left/unloaded
static void leave(void* screen) {
//remove buttons and touch area.
gui_button_remove(&b_back);
gui_button_remove(&b_save);
touch_unregister_area(&a_area);
}
//Callback for when the screen should be updated
//This is the main loop of the screen. This method will be called repeatedly
static void update(void* screen) {
//Note: The only way to detect that pixy has been disconnected is if a command fails. There's no pixy_is_connected method yet :'(
if(!pixy_connected) { //Pixy not connected
pixy_close(); //Ensure that all pixy resources are freed (failsafe)
if(pixy_init()==0) { //try to connect to pixy
pixy_connected=true;
if(!subMenu) { //we're not coming from a submenu
pixy_pos.x=pixy_pos.y=500; //reset servo positions to center
}
printf("pixy (re)initialized\n");
}
}
if(pixy_connected) { //If we are connected (now)
pixy_service(); //Handle pending pixy events (e.g. color info retrival)
pixy_render_full_frame(1,40); //render the pixy video at point (1,40)
//set the servo positions to the coordinates form the touch interrupt
pixy_rcs_set_position(0,pixy_pos.x);
pixy_rcs_set_position(1,pixy_pos.y);
}
}
//Declare screen callbacks
static SCREEN_STRUCT screen = {
enter,
leave,
update
};
SCREEN_STRUCT* get_screen_photomode() {
return &screen;
}

View File

@@ -0,0 +1,22 @@
#include "screen.h"
/**
* @addtogroup screens
*/
/*@{*/
/**
* @defgroup photomode Photo Mode (Screen)
* The Photo Mode Screen allows taking snapshots of the current pixy cam feed
*/
/*@{*/
/**
* Returns a pointer to the photomode screen
* \sa gui_screen_navigate
* @return
*/
SCREEN_STRUCT* get_screen_photomode();
/*@}*/
/*@}*/

View File

@@ -0,0 +1,301 @@
#include "screen_photomode_save.h"
#include "filesystem.h"
#include "button.h"
#include "tft.h"
#include "touch.h"
#include "pixy.h"
#include "pixy_helper.h"
#include <stdlib.h>
#include <string.h>
static BUTTON_STRUCT b_back; //Button to navigate back
static TOUCH_AREA_STRUCT a_area; //Touch area to select the save-file
//Callback for when the user presses the "back" button
static void b_back_cb(void* button) {
gui_screen_back();
}
static int num_files_ok; //number of files into which we can write the image (size, flags ok)
static enum {init, error, showlist, picking, saving, done} state; //Current state of the screen state machine
static int fontheight; //The space between one line of text to the next
static int liststart; //The y-Coordinate of the Start of the File-List
static const char* picked_file; //The filename picked by the user, to save the image to
//Linked list structure to save all files which are suitable for saving.
typedef struct FILE_LIST_ENTRY_S{
char* filename; //Name of the file
struct FILE_LIST_ENTRY_S* next; //Pointer to the next entry in the list or NULL
} FILE_LIST_ENTRY;
static FILE_LIST_ENTRY* files_ok; //Pointer to the head of the list
//Callback for when the user selects a file to save the image into
static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction) {
int y = touch_get_last_point().y-liststart; //Calculate the y-Coordinate of the touch point relative to the start of the file-list
int elem = y/fontheight; //Calculate the file index
if(elem<0 | elem>= num_files_ok) return; //Check if the file index is valid (0,1,..,num_files_ok-1)
//Search for the corresponding entry in the linked list
FILE_LIST_ENTRY* current_entry = files_ok; //Start walking through the list, starting by the head of the list
for(int i=0; i<elem; i++) { //Until we have reached the file (index)
current_entry= current_entry->next; //traverse to the next file
}
picked_file = current_entry->filename; //save the picked filename. It will be used by the statemachine in the main loop
touch_unregister_area(&a_area); //unregister the touch area, we no longer need it. No more interrupts will be fired.
state=saving; //Change the state of the statemachine
}
//Text-Lines to show if we have no matching files (num_files_ok==0)
static const char* nomatch_text [] = {
"Due to limitations of the filesystem",
"implementation you can only write to",
"existing files.",
"",
"The files need to have a .bmp",
"extension and must be at least",
"189410 bytes (185kb) large.",
"Unfortunately there were no such",
"files found in the root directory.",
"",
"Please create some files and come",
"back again.",
NULL
};
//Bitmap header for a 318x198x24bit windows bitmap. data starts at 0x7A (= after this header)
//This header has been taken from a white bitmap saved with gimp.
//Wikipedia has a pretty good description on the header: http://de.wikipedia.org/wiki/Windows_Bitmap
static unsigned char bmpheader_data[0x7A] = {
0x42, 0x4d, 0xe2, 0xe3, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x00,
0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x3e, 0x01, 0x00, 0x00, 0xc6, 0x00,
0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xe3,
0x02, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x47, 0x52, 0x73, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00
};
//Callback for when the screen is entered/loaded
static void enter(void* screen) {
tft_clear(WHITE);
#define X_OFS 5
//Back button
b_back.base.x1=X_OFS; //Start X of Button
b_back.base.y1=210; //Start Y of Button
b_back.base.x2=AUTO; //Auto Calculate X2 with String Width
b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height
b_back.txtcolor=WHITE; //Set foreground color
b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
b_back.font=0; //Select Font
b_back.text="Back"; //Set Text (For formatted strings take sprintf)
b_back.callback=b_back_cb; //Call b_back_cb as Callback
gui_button_add(&b_back); //Register Button (and run the callback from now on)
state =init; //Start with the init state
fontheight = tft_font_height(0)+2; //Save the height of the used font, for fast access
files_ok = NULL; //initialize the linked list with 0 elements
num_files_ok = 0; //we have zero! elements
}
//Callback for when the screen should be updated
//This is the main loop of the screen. This method will be called repeatedly
static void update(void* screen) {
switch(state) {
case init: //Init State: The user just entered the screen
{
DIRECTORY_STRUCT* dir = filesystem_dir_open("."); //open root directory
if(dir==NULL) { //error while opening root directory
tft_print_line(X_OFS,5,BLACK,TRANSPARENT,0,"Error accessing Filesystem");
state=error;
break;
}
bool nomatch= true; //whether or not we have zero files which are suitable for saving
for(int i=0; i<dir->num_files; i++) { //walk through all files in the directory
FILE_STRUCT* file = &(dir->files[i]); //Pointer to the current file/subdirectory
//Ignore directories, archives, hidden files, system files and files we cannot write to
if(file->fattrib&(F_SYS|F_HID|F_ARC|F_DIR|F_RDO)) continue;
//ignore files which are not large enough
if(file->fsize<189410) continue; //size taken from an example bitmap (318x198x24)
nomatch=false; //at least one file matches
break;
}
if(nomatch) { //not one file is suitable for writing
int y=5; //y-Coordinate where to start writing the error text
int i=0;
while(nomatch_text[i]!=NULL) { //for every line in the big error array
//Write the line's text and go to the next line
tft_print_line(X_OFS,y+i*fontheight,BLACK,TRANSPARENT,0,nomatch_text[i]);
i++;
}
state = error;
} else { //we have a least one suitable file
state = showlist;
}
filesystem_dir_close(dir); //free directory struct
}
break;
case showlist: //Show List State: Where we load and present the suitable file's to the user in a list
{
DIRECTORY_STRUCT* dir2 = filesystem_dir_open("."); //Open the directory again
if(dir2==NULL) return; //Error on opening? This should never happen, since it's handled in the previous state
int y = 5; //y-Coordinate where to start drawing/writing text/list-elements
tft_print_line(X_OFS,y,BLACK,TRANSPARENT,0,"Pick a file to save the image to");
y+=fontheight+5;
tft_print_line(X_OFS,y,BLUE,TRANSPARENT,0,"Name Modified Size");
y+=fontheight;
liststart = y; //store the y coordinate of the start of the list away (used in toucharea callback)
num_files_ok = 0; //we start with 0 matching files
FILE_LIST_ENTRY* current_entry = NULL; //We start with an empty list
for(int i=0; i<dir2->num_files && num_files_ok<10; i++) { //go through all the files of the directory, abort if we have 10 matches
FILE_STRUCT* file = &(dir2->files[i]);
//Ignore directories, archives, hidden files, system files and files we cannot write to
if(file->fattrib&(F_SYS|F_HID|F_ARC|F_DIR|F_RDO)) continue;
//ignore files which are not large enough
if(file->fsize<189410) continue; //size taken from an example bitmap (318x198x24)
//Print out filename, modified date,time and file size
tft_print_formatted(X_OFS,y,BLACK,
TRANSPARENT,0,"%-16s %02u.%02u.%02u %02u:%02u:%02u %u",
file->fname,
file->fdate.day,
file->fdate.month,
(file->fdate.year+1980)%100,
file->ftime.hour,
file->ftime.min,
file->ftime.sec*2,
file->fsize);
if(current_entry==NULL) { //The list is empty
current_entry = malloc(sizeof(FILE_LIST_ENTRY)); //create new entry
files_ok = current_entry; //assign it to the list head
} else { //there's a least one entry in the list
current_entry->next = malloc(sizeof(FILE_LIST_ENTRY)); //append entry to previous entry
current_entry = current_entry->next; //newly created entry is the current now.
}
current_entry->next = NULL; //we're at the end of the list (for now)
current_entry->filename = malloc(strlen(file->fname)+1); //allocate space for the filename + zero-termination
strcpy(current_entry->filename,file->fname); //copy filename (so that we can close the directory after scanning)
//since we have found a suitable file we need to increment the position in the list
num_files_ok++;
y+=fontheight;
}
//Touch area for file-selection (in the list)
a_area.hookedActions = PEN_UP; //we're only interested in PEN_UP events
a_area.x1 = X_OFS; //Left border
a_area.y1 = liststart; //Start where the list started
a_area.x2 = 320-X_OFS; //Right border
a_area.y2 = liststart+fontheight*num_files_ok; //stop at the end of the list
a_area.callback = touchCB; //execute our callback when PEN_UP occurs
touch_register_area(&a_area); //register the touch area and receive events from now on
filesystem_dir_close(dir2); //we no longer need the directory struct, since we have our own linked list now
state=picking;
}
break;
case picking: //Picking State: Where we wait on the users file choice
pixy_service(); //Handle pending pixy events
//do nothing and wait on user to pick a file
break;
case saving: //Saving State: Where we save the image to the selected file
{
FILE_HANDLE* file = filesystem_file_open(picked_file); //try to open the selected file
if(file==NULL) { //opening the file failed
tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Could not open %s",picked_file);
state=error;
break;
}
filesystem_file_seek(file,0); //seek to the start of the file (optional?)
if(filesystem_file_write(file,bmpheader_data,0x7A)!=F_OK) { //Writing the header failed
tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Error while writing to %s",picked_file);
filesystem_file_close(file);
state=error;
break;
}
if(pixy_save_full_frame(file)!=0) { //Writing the imagedata failed
tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Error while writing to %s",picked_file);
filesystem_file_close(file);
state=error;
break;
}
//if we reach this point, we have written all data out successfully
filesystem_file_close(file); //close/finalize the file
tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Image saved to %s",picked_file);
state = done;
}
break;
case error: //Error State: Where we show an error message and leave the user no other choice than to click the backbutton
case done: //Done State: When saving the file was successful
pixy_service(); //Handle pending pixy events
//wait on user to click the back button
break;
}
}
//Callback for when the screen is left/unloaded
static void leave(void* screen) {
gui_button_remove(&b_back); //Remove/Free the back button
if(state==picking){ //The user left the screen in the "picking"-phase
touch_unregister_area(&a_area); //remove the touch area (for the list)
}
if(state==picking|| state==saving || state==done) { //the user left the screen after we created the linked list
//Iterate through the linked list and free all resources
FILE_LIST_ENTRY* current_entry = files_ok; //start with the list head
while(current_entry!=NULL) { //while we're not at the end
FILE_LIST_ENTRY* temp = current_entry->next; //save the next pointer because we free the current element on the next line
free((void*)(current_entry->filename)); //free filename
free(current_entry); //free element itself
current_entry= temp; //advance
}
}
}
//Declare screen callbacks
static SCREEN_STRUCT screen = {
enter,
leave,
update
};
SCREEN_STRUCT* get_screen_photomodesave() {
return &screen;
}

View File

@@ -0,0 +1,23 @@
#include "screen.h"
/**
* @addtogroup screens
*/
/*@{*/
/**
* @defgroup photomodesave Photo Mode Save (Screen)
* The Photo Mode Save Screen helps the user saving a file to the filesystem
*/
/*@{*/
/**
* Returns a pointer to the photomode save screen
* \sa gui_screen_navigate
* @return
*/
SCREEN_STRUCT* get_screen_photomodesave();
/*@}*/
/*@}*/

View File

@@ -1,173 +1,329 @@
#include "screen_pixytest.h"
#include "button.h"
#include "numupdown.h"
#include "tft.h"
#include "touch.h"
#include "pixy.h"
#include <stdlib.h>
#include "system.h"
#include "pixy_helper.h"
static volatile bool pixy_connected = false;
static volatile enum {detecting, idle,update_servos, update_ledcolor, update_ledcurrent} state; //Current state of the screen state machine
static BUTTON_STRUCT b_back;
static BUTTON_STRUCT b_runstop;
static TOUCH_AREA_STRUCT a_area;
static BUTTON_STRUCT b_servos_center;
static BUTTON_STRUCT b_servos_topleft;
static BUTTON_STRUCT b_servos_topright;
static BUTTON_STRUCT b_servos_bottomleft;
static BUTTON_STRUCT b_servos_bottomright;
static uint16_t servo_x;
static uint16_t servo_y;
static BUTTON_STRUCT b_led_off;
static BUTTON_STRUCT b_led_white;
static BUTTON_STRUCT b_led_red;
static BUTTON_STRUCT b_led_green;
static BUTTON_STRUCT b_led_blue;
static uint32_t led_color;
static uint32_t led_maxcurrent;
static NUMUPDOWN_STRUCT n_led_powerlimit;
static void b_back_cb(void* button) {
gui_screen_back();
}
static volatile bool pixy_running = false;
static bool old_pixy_running= false;
static void b_runstop_cb(void* button) {
pixy_running=!pixy_running;
}
static POINT_STRUCT pixy_pos;
static POINT_STRUCT old_pos;
static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction) {
POINT_STRUCT p = touch_get_last_point();
switch(triggeredAction) {
case PEN_ENTER:
case PEN_DOWN:
old_pos = p;
break;
case PEN_MOVE:
{
int16_t deltaX = p.x - old_pos.x;
int16_t deltaY = p.y - old_pos.y;
old_pos=p;
printf("%d %d\n",deltaX,deltaY);
if(pixy_connected) {
int16_t new_x = pixy_pos.x+deltaX*2;
int16_t new_y = pixy_pos.y-deltaY*2;
if(new_x<0) new_x=0;
if(new_x>1000) new_x=1000;
if(new_y<0) new_y=0;
if(new_y>1000) new_y=1000;
pixy_pos.x = new_x;
pixy_pos.y= new_y;
}
static void b_servos_center_cb(void* button) {
if(state==idle) {
servo_x=500;
servo_y=500;
state=update_servos;
}
break;
case PEN_UP:
case PEN_LEAVE:
printf("Leave/up\n");
default: break;
}
}
static void b_servos_topleft_cb(void* button) {
if(state==idle) {
servo_x=0;
servo_y=0;
state=update_servos;
}
}
static void b_servos_topright_cb(void* button) {
if(state==idle) {
servo_x=1000;
servo_y=0;
state=update_servos;
}
}
static void b_servos_bottomleft_cb(void* button) {
if(state==idle) {
servo_x=0;
servo_y=1000;
state=update_servos;
}
}
static void b_servos_bottomright_cb(void* button) {
if(state==idle) {
servo_x=1000;
servo_y=1000;
state=update_servos;
}
}
static void b_led_off_cb(void* button) {
if(state==idle) {
led_color=0x000000;
state=update_ledcolor;
}
}
static void b_led_white_cb(void* button) {
if(state==idle) {
led_color=0xFFFFFF;
state=update_ledcolor;
}
}
static void b_led_red_cb(void* button) {
if(state==idle) {
led_color=0xFF0000;
state=update_ledcolor;
}
}
static void b_led_green_cb(void* button) {
if(state==idle) {
led_color=0x00FF00;
state=update_ledcolor;
}
}
static void b_led_blue_cb(void* button) {
if(state==idle) {
led_color=0x0000FF;
state=update_ledcolor;
}
}
static void n_led_powerlimit_cb(void* numupdown, int16_t value) {
if(state==idle) {
led_maxcurrent=value;
state=update_ledcurrent;
}
}
static void enter(void* screen) {
tft_clear(WHITE);
//Back button
b_back.base.x1=10; //Start X of Button
b_back.base.y1=210; //Start Y of Button
b_back.base.x2=AUTO; //b_back.base.x1+160; //Auto Calculate X2 with String Width
b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height
b_back.txtcolor=WHITE; //Set foreground color
b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
b_back.font=0; //Select Font
b_back.text="Back"; //Set Text (For formatted strings take sprintf)
b_back.callback=b_back_cb; //Call b_back_cb as Callback
gui_button_add(&b_back); //Register Button (and run the callback from now on)
b_back.base.y1=210; //Start Y of Button
b_back.base.x2=AUTO; //Auto Calculate X2 with String Width
b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height
b_back.txtcolor=WHITE; //Set foreground color
b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
b_back.font=0; //Select Font
b_back.text="Back"; //Set Text (For formatted strings take sprintf)
b_back.callback=b_back_cb; //Call b_back_cb as Callback
gui_button_add(&b_back); //Register Button (and run the callback from now on)
//Back button
b_runstop.base.x1=60; //Start X of Button
b_runstop.base.y1=210; //Start Y of Button
b_runstop.base.x2=AUTO; //b_runstop.base.x1+160; //Auto Calculate X2 with String Width
b_runstop.base.y2=AUTO; //Auto Calculate Y2 with String Height
b_runstop.txtcolor=WHITE; //Set foreground color
b_runstop.bgcolor=HEX(0xAE1010); //Set runstopground color (Don't take 255 or 0 on at least one channel, to make shadows possible)
b_runstop.font=0; //Select Font
b_runstop.text="Run/Stop"; //Set Text (For formatted strings take sprintf)
b_runstop.callback=b_runstop_cb; //Call b_runstop_cb as Callrunstop
gui_button_add(&b_runstop); //Register Button (and run the callrunstop from now on)
//Servo stuff
#define SERVO_BUTTON_Y 10
#define SERVO_BUTTON_SPACING 5
tft_print_line(5,SERVO_BUTTON_Y,BLACK,TRANSPARENT,0,"Servos:");
b_servos_center.base.x1=55;
b_servos_center.base.y1=SERVO_BUTTON_Y-3;
b_servos_center.base.x2=AUTO;
b_servos_center.base.y2=AUTO;
b_servos_center.txtcolor=WHITE;
b_servos_center.bgcolor=HEX(0xAE1010);
b_servos_center.font=0;
b_servos_center.text="Center";
b_servos_center.callback=b_servos_center_cb;
gui_button_add(&b_servos_center);
//Area test
a_area.hookedActions = PEN_DOWN | PEN_MOVE | PEN_ENTER | PEN_UP | PEN_LEAVE;
a_area.x1 = 0;
a_area.y1 = 0;
a_area.x2 = 317;
a_area.y2 = 197;
a_area.callback = touchCB;
touch_register_area(&a_area);
b_servos_topleft.base.x1=b_servos_center.base.x2+SERVO_BUTTON_SPACING;
b_servos_topleft.base.y1=SERVO_BUTTON_Y-3;
b_servos_topleft.base.x2=AUTO;
b_servos_topleft.base.y2=AUTO;
b_servos_topleft.txtcolor=WHITE;
b_servos_topleft.bgcolor=HEX(0xAE1010);
b_servos_topleft.font=0;
b_servos_topleft.text="ToLe";
b_servos_topleft.callback=b_servos_topleft_cb;
gui_button_add(&b_servos_topleft);
b_servos_topright.base.x1=b_servos_topleft.base.x2+SERVO_BUTTON_SPACING;
b_servos_topright.base.y1=SERVO_BUTTON_Y-3;
b_servos_topright.base.x2=AUTO;
b_servos_topright.base.y2=AUTO;
b_servos_topright.txtcolor=WHITE;
b_servos_topright.bgcolor=HEX(0xAE1010);
b_servos_topright.font=0;
b_servos_topright.text="ToRi";
b_servos_topright.callback=b_servos_topright_cb;
gui_button_add(&b_servos_topright);
b_servos_bottomleft.base.x1=b_servos_topright.base.x2+SERVO_BUTTON_SPACING;
b_servos_bottomleft.base.y1=SERVO_BUTTON_Y-3;
b_servos_bottomleft.base.x2=AUTO;
b_servos_bottomleft.base.y2=AUTO;
b_servos_bottomleft.txtcolor=WHITE;
b_servos_bottomleft.bgcolor=HEX(0xAE1010);
b_servos_bottomleft.font=0;
b_servos_bottomleft.text="BoLe";
b_servos_bottomleft.callback=b_servos_bottomleft_cb;
gui_button_add(&b_servos_bottomleft);
b_servos_bottomright.base.x1=b_servos_bottomleft.base.x2+SERVO_BUTTON_SPACING;
b_servos_bottomright.base.y1=SERVO_BUTTON_Y-3;
b_servos_bottomright.base.x2=AUTO;
b_servos_bottomright.base.y2=AUTO;
b_servos_bottomright.txtcolor=WHITE;
b_servos_bottomright.bgcolor=HEX(0xAE1010);
b_servos_bottomright.font=0;
b_servos_bottomright.text="BoRi";
b_servos_bottomright.callback=b_servos_bottomright_cb;
gui_button_add(&b_servos_bottomright);
//Led Color stuff
#define LED_COLOR_BUTTON_Y 35
#define LED_COLOR_BUTTON_SPACING 5
tft_print_line(5,LED_COLOR_BUTTON_Y,BLACK,TRANSPARENT,0,"Led Color:");
b_led_off.base.x1=85;
b_led_off.base.y1=LED_COLOR_BUTTON_Y-3;
b_led_off.base.x2=AUTO;
b_led_off.base.y2=AUTO;
b_led_off.txtcolor=WHITE;
b_led_off.bgcolor=BLACK;
b_led_off.font=0;
b_led_off.text="Off";
b_led_off.callback=b_led_off_cb;
gui_button_add(&b_led_off);
b_led_white.base.x1=b_led_off.base.x2+LED_COLOR_BUTTON_SPACING;
b_led_white.base.y1=LED_COLOR_BUTTON_Y-3;
b_led_white.base.x2=AUTO;
b_led_white.base.y2=AUTO;
b_led_white.txtcolor=BLACK;
b_led_white.bgcolor=HEX(0xEEEEEE);
b_led_white.font=0;
b_led_white.text="White";
b_led_white.callback=b_led_white_cb;
gui_button_add(&b_led_white);
b_led_red.base.x1=b_led_white.base.x2+LED_COLOR_BUTTON_SPACING;
b_led_red.base.y1=LED_COLOR_BUTTON_Y-3;
b_led_red.base.x2=AUTO;
b_led_red.base.y2=AUTO;
b_led_red.txtcolor=WHITE;
b_led_red.bgcolor=HEX(0xEE0000);
b_led_red.font=0;
b_led_red.text="Red";
b_led_red.callback=b_led_red_cb;
gui_button_add(&b_led_red);
b_led_green.base.x1=b_led_red.base.x2+LED_COLOR_BUTTON_SPACING;
b_led_green.base.y1=LED_COLOR_BUTTON_Y-3;
b_led_green.base.x2=AUTO;
b_led_green.base.y2=AUTO;
b_led_green.txtcolor=WHITE;
b_led_green.bgcolor=HEX(0x00EE00);
b_led_green.font=0;
b_led_green.text="Green";
b_led_green.callback=b_led_green_cb;
gui_button_add(&b_led_green);
b_led_blue.base.x1=b_led_green.base.x2+LED_COLOR_BUTTON_SPACING;
b_led_blue.base.y1=LED_COLOR_BUTTON_Y-3;
b_led_blue.base.x2=AUTO;
b_led_blue.base.y2=AUTO;
b_led_blue.txtcolor=WHITE;
b_led_blue.bgcolor=HEX(0x0000EE);
b_led_blue.font=0;
b_led_blue.text="Blue";
b_led_blue.callback=b_led_blue_cb;
gui_button_add(&b_led_blue);
//Led MaxPower stuff
#define LED_POWER_BUTTON_Y 70
tft_print_line(5,LED_POWER_BUTTON_Y,BLACK,TRANSPARENT,0,"Led Maximum Current:");
//Num up down test
n_led_powerlimit.x=160;
n_led_powerlimit.y=LED_POWER_BUTTON_Y-7;
n_led_powerlimit.fgcolor=WHITE;
n_led_powerlimit.value = 10;
n_led_powerlimit.max=40;
n_led_powerlimit.min =0;
n_led_powerlimit.callback=n_led_powerlimit_cb;
gui_numupdown_add(&n_led_powerlimit);
//Pixy stuff
pixy_connected = (pixy_init()==0); //try to connect to pixy
if(pixy_connected) {
pixy_pos.x=pixy_pos.y=500;
}
state=detecting;
}
static void leave(void* screen) {
gui_button_remove(&b_back);
gui_button_remove(&b_runstop);
touch_unregister_area(&a_area);
}
gui_button_remove(&b_servos_center);
gui_button_remove(&b_servos_topleft);
gui_button_remove(&b_servos_topright);
gui_button_remove(&b_servos_bottomleft);
gui_button_remove(&b_servos_bottomright);
gui_button_remove(&b_led_off);
gui_button_remove(&b_led_white);
gui_button_remove(&b_led_red);
gui_button_remove(&b_led_green);
gui_button_remove(&b_led_blue);
gui_numupdown_remove(&n_led_powerlimit);
int pixy_led_test();
int pixy_frame_test();
}
static void update(void* screen) {
//Note: The only way to detect that pixy has been disconnected is if a command fails. There's no pixy_is_connected method yet :'(
if(!pixy_connected) { //Pixy not connected
pixy_close(); //Ensure that all pixy resources are freed (failsafe)
if(pixy_init()==0) { //try to connect to pixy
pixy_connected=true;
pixy_pos.x=pixy_pos.y=500;
printf("pixy reinitialized\n");
}
}
if(pixy_connected) {
pixy_service(); //Send/receive event data from/to pixy failed
if(pixy_frame_test()!=0) {
pixy_connected=false;
}
/*if(pixy_led_test()!=0) {
pixy_connected=false;
}*/
if(!pixy_running) {
pixy_rcs_set_position(0,pixy_pos.x);
pixy_rcs_set_position(1,pixy_pos.y);
}
if(pixy_running!=old_pixy_running) {
old_pixy_running=pixy_running;
if(pixy_running) { //start tracking
int32_t response;
int return_value;
return_value = pixy_command("runprog", INT8(2), END_OUT_ARGS, &response, END_IN_ARGS);
} else { //stop tracking
switch(state) {
case detecting: //Detecting State: Where we try to connect to the pixy
if(pixy_init()==0) { //Pixy connection ok
int32_t response;
int return_value;
return_value = pixy_command("stop", END_OUT_ARGS, &response, END_IN_ARGS);
pixy_led_set_max_current(10);
state = idle; //Go to next state
}
break;
case idle:
pixy_service();
break;
case update_servos:
pixy_rcs_set_position(0,servo_x);
pixy_rcs_set_position(1,servo_y);
state = idle;
break;
case update_ledcolor:
{
int32_t response;
int return_value;
return_value = pixy_command("led_set", INT32(led_color), END_OUT_ARGS, &response, END_IN_ARGS);
state = idle;
}
//system_delay(500);
}
break;
case update_ledcurrent:
pixy_led_set_max_current(led_maxcurrent);
state = idle;
break;
}
}
@@ -184,157 +340,3 @@ SCREEN_STRUCT* get_screen_pixytest() {
//-----------------------------------------------------------------------------------------------------------------
int colorind;
const uint32_t colors [] = {0xFF0000, 0x00FF00,0x0000FF,0xFFFF00,0x00FFFF,0xFF00FF,0xFFFFFF,0x000000};
const int num_colors = sizeof(colors)/sizeof(uint32_t);
int pixy_led_test() {
if(colorind==0) {
pixy_led_set_max_current(5);
}
int32_t response;
int return_value;
return_value = pixy_command("led_set", INT32(colors[colorind++]), END_OUT_ARGS, &response, END_IN_ARGS);
colorind%=num_colors;
if(return_value!=0) {
colorind=0; //reset color ind, to start at zero when plugging pixy in again
}
return return_value;
}
//----------------------------------------------------------------------------------------------------------------------------
int renderBA81(uint8_t renderFlags, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame);
int pixy_frame_test() {
uint8_t* videodata;
int32_t response;
int32_t fourccc;
int8_t renderflags;
uint16_t xwidth;
uint16_t ywidth;
uint32_t size;
int return_value = pixy_command("cam_getFrame", // String id for remote procedure
INT8(0x21), // mode
INT16(0), // xoffset
INT16(0), // yoffset
INT16(320), // width
INT16(200), // height
END_OUT_ARGS, // separator
&response, // pointer to mem address for return value
&fourccc,
&renderflags,
&xwidth,
&ywidth,
&size,
&videodata, // pointer to mem address for returned frame
END_IN_ARGS);
if(return_value==0) {
return_value = renderBA81(renderflags,xwidth,ywidth,size,videodata);
}
return return_value;
}
void interpolateBayer(uint16_t width, uint16_t x, uint16_t y, uint8_t *pixel, uint8_t* r, uint8_t* g, uint8_t* b)
{
if (y&1)
{
if (x&1)
{
*r = *pixel;
*g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2;
*b = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2;
}
else
{
*r = (*(pixel-1)+*(pixel+1))>>1;
*g = *pixel;
*b = (*(pixel-width)+*(pixel+width))>>1;
}
}
else
{
if (x&1)
{
*r = (*(pixel-width)+*(pixel+width))>>1;
*g = *pixel;
*b = (*(pixel-1)+*(pixel+1))>>1;
}
else
{
*r = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2;
*g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2;
*b = *pixel;
}
}
}
int renderBA81(uint8_t renderFlags, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame)
{
uint16_t x, y;
uint8_t r, g, b;
// skip first line
frame += width;
// don't render top and bottom rows, and left and rightmost columns because of color
// interpolation
//uint32_t decodedimage[(width-2)*(height-2)];
uint16_t* decodedimage = malloc(sizeof(uint16_t)*(width-2)*(height-2));
if(decodedimage==NULL) { //not enough free space to decode image in memory
//decode & render image pixel by pixel
uint16_t* line = decodedimage;
for (y=1; y<height-1; y++)
{
frame++;
for (x=1; x<width-1; x++, frame++)
{
interpolateBayer(width, x, y, frame, &r, &g, &b);
tft_draw_pixel(x-1,y-1,RGB(r,g,b));
}
frame++;
}
} else { //enough space
uint16_t* line = decodedimage;
for (y=1; y<height-1; y++)
{
//line = (unsigned int *)img.scanLine(y-1);
frame++;
for (x=1; x<width-1; x++, frame++)
{
interpolateBayer(width, x, y, frame, &r, &g, &b);
//*line++ = (0xff<<24) | (r<<16) | (g<<8) | (b<<0);
*line++ = RGB(r,g,b);
}
frame++;
}
tft_draw_bitmap_unscaled(0,0,width-2,height-2,decodedimage);
free(decodedimage);
}
return 0;
}

View File

@@ -6,7 +6,7 @@
/*@{*/
/**
* @defgroup pixytest Pixytest
* @defgroup pixytest Pixytest (Screen)
* The Pixy-Test Screen tests the pixy module.
*/
/*@{*/
@@ -18,4 +18,5 @@
*/
SCREEN_STRUCT* get_screen_pixytest();
/*@}@}*/
/*@}*/
/*@}*/

View File

@@ -0,0 +1,349 @@
#include "screen_tracking.h"
#include "button.h"
#include "checkbox.h"
#include "tft.h"
#include "touch.h"
#include "pixy.h"
#include "system.h"
#include "pixy_helper.h"
static BUTTON_STRUCT b_back; //Button to navigate back
static BUTTON_STRUCT b_select; //Button to start the color region selection
static CHECKBOX_STRUCT c_frame_toggle; //Checkbox to toggle video data on/off
static TOUCH_AREA_STRUCT a_area; //Touch area for the color region selection
//Callback for when the user presses the "back" button
static void b_back_cb(void* button) {
gui_screen_back(); //navigate back to the previous screen
}
static volatile bool frame_visible = false; //Whether or not the video data should be displayed
static void c_frame_toggle_cb(void *checkbox, bool checked) {
frame_visible=checked; //Set the visibility of the frame to the checked state of the checkbox
//Frame will be drawn in the main loop below
}
static enum {detecting, init, tracking, preselecting, abortselecting, selecting, selected, error} state; //Current state of the screen state machine
static POINT_STRUCT point1; //First point of the rectangle selected by the user (color region selection)
static POINT_STRUCT point2; //End point of the rectangle selected by the user (color region selection)
static bool point1_valid; //Whether or not we have a valid first point
//Callback for when the user presses the "select color" button
static void b_select_cb(void* button) {
if(state==selecting) { //we're currently selecting a color region
state = abortselecting; //Abort selecting!!
} else if (state==tracking) { //we're currently watching the tracking
state = preselecting; //start selecting
}
}
//Video Region properties
//The camera records with 320*200px, but we need to keep a 1px border because of color interpolation (bayer format)
#define FRAME_START_X 1 //x-Coordinate of the top-left point of the frame rectangle on display
#define FRAME_START_Y 41 //y-Coordinate of the top-left point of the frame rectangle on display
#define FRAME_WIDTH 318 //Width of the video frame
#define FRAME_HEIGHT 198 //Height of the video frame
#define FRAME_END_X FRAME_START_X +FRAME_WIDTH-1 //x-Coordinate of the bottom-right point of the frame rectangle
#define FRAME_END_Y FRAME_START_Y +FRAME_HEIGHT-1 //y-Coordinate of the bottom-right point of the frame rectangle
//Callback for when the user touches the frame area to select a color region.
//Note: It doesn't matter in which direction the user draws the rectangle, we'll normalize the coordinates later
static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction) {
POINT_STRUCT p = touch_get_last_point();
switch(triggeredAction) {
case PEN_DOWN: //The user just put down the pen
point1.x = p.x-FRAME_START_X; //Calculate x-Coordinate relative to frame start
point1.y = p.y-FRAME_START_Y; //Calculate y-Coordinate relative to frame start
point1_valid= true; //The point1 is now valid
break;
case PEN_UP: //The user took the pen away
if(point1_valid) { //only execute if point1 is valid
point2.x = p.x-FRAME_START_X; //Calculate x-Coordinate relative to frame start
point2.y = p.y-FRAME_START_Y; //Calculate y-Coordinate relative to frame start
state = selected;
}
break;
}
}
//Prototype for tracking start/stop methods
typedef void (*TRACKING_VOID_CALLBACK)(void* tracking_config);
//Prototype for tracking update method
typedef void (*TRACKING_BLOCK_CALLBACK)(void* tracking_config, struct Block* blocks, int num_blocks );
//Structure to save callbacks and settings of a tracking implementation
typedef struct {
TRACKING_VOID_CALLBACK start;
TRACKING_VOID_CALLBACK stop;
TRACKING_BLOCK_CALLBACK update;
} TRACKING_CONFIG_STRUCT;
//Methods for our tracking implementation ahead
//Method/Callback to start our tracking
void tracking_our_start(void* tracking_config) {
//Activate pixy's data send program
int32_t response;
int return_value;
return_value = pixy_command("runprog", INT8(0), END_OUT_ARGS, &response, END_IN_ARGS);
}
//Method/Callback to stop our tracking
void tracking_our_stop(void* tracking_config) {
//Stop pixy's data send programm
int32_t response;
int return_value;
return_value = pixy_command("stop", END_OUT_ARGS, &response, END_IN_ARGS);
}
//Method/Callback to calculate one step of our tracking
void tracking_our_update(void* tracking_config, struct Block* blocks, int num_blocks) {
//TODO: Implement tracking!
//Calculate new servo pos and set the new servo pos
}
//Variable which stores all the callbacks and settings for our tracking implementation
static TRACKING_CONFIG_STRUCT tracking_our = {
tracking_our_start,
tracking_our_stop,
tracking_our_update
};
//Methods for reference tracking implementation ahead
//Method/Callback to start reference tracking
void tracking_reference_start(void* tracking_config) {
//Run reference tracking
int32_t response;
int return_value;
return_value = pixy_command("runprog", INT8(2), END_OUT_ARGS, &response, END_IN_ARGS);
}
//Method/Callback to stop reference tracking
void tracking_reference_stop(void* tracking_config) {
//Stop reference tracking
int32_t response;
int return_value;
return_value = pixy_command("stop", END_OUT_ARGS, &response, END_IN_ARGS);
}
//Method/Callback to calculate one step of the reference tracking
void tracking_reference_update(void* tracking_config, struct Block* blocks, int num_blocks) {
//Nothing to do here. Pixy does it all.
}
//Variable which stores all the callbacks and settings for the reference tracking implementation
static TRACKING_CONFIG_STRUCT tracking_reference = {
tracking_reference_start,
tracking_reference_stop,
tracking_reference_update
};
//Pointer to the currently active tracking implementation. See also tracking_set_mode
static TRACKING_CONFIG_STRUCT* tracking_current;
//Method to set the current tracking implementation. This function is exported and should be called before getting the screen
void tracking_set_mode(enum Tracking_Implementation impl) {
//Depending on the enum value let tracking_current point to a different setting/callback structure
switch(impl) {
case OUR_TRACKING:
tracking_current = &tracking_our;
break;
case REFERENCE_TRACKING:
tracking_current = &tracking_reference;
break;
default:
tracking_current=NULL;
break;
}
}
//Callback for when the screen is entered/loaded
static void enter(void* screen) {
tft_clear(WHITE);
//"Back" button
b_back.base.x1=5; //Start X of Button
b_back.base.y1=5; //Start Y of Button
b_back.base.x2=AUTO; //Auto Calculate X2 with String Width
b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height
b_back.txtcolor=WHITE; //Set foreground color
b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
b_back.font=0; //Select Font
b_back.text="Back"; //Set Text (For formatted strings take sprintf)
b_back.callback=b_back_cb; //Call b_back_cb as Callback
gui_button_add(&b_back); //Register Button (and run the callback from now on)
//"Select color" button
b_select.base.x1=150;
b_select.base.y1=5;
b_select.base.x2=AUTO;
b_select.base.y2=AUTO;
b_select.txtcolor=WHITE;
b_select.bgcolor=HEX(0xAE1010);
b_select.font=0;
b_select.text="Select Color";
b_select.callback=b_select_cb;
gui_button_add(&b_select);
//"Frame visible" checkbox
c_frame_toggle.base.x1 = 50;
c_frame_toggle.base.x2 = 50+16;
c_frame_toggle.base.y1 = 5;
c_frame_toggle.base.y2 = 5+16;
c_frame_toggle.checked = frame_visible;
c_frame_toggle.fgcolor = CHECKBOX_WIN_FG_COLOR;
c_frame_toggle.callback = c_frame_toggle_cb;
gui_checkbox_add(&c_frame_toggle);
tft_print_line(73,8,BLACK,TRANSPARENT,0,"Show Video");
//Area to select a "color region"
a_area.hookedActions = PEN_DOWN | PEN_UP;
a_area.x1 = FRAME_START_X;
a_area.y1 = FRAME_START_Y;
a_area.x2 = FRAME_END_X;
a_area.y2 = FRAME_END_Y;
a_area.callback = touchCB;
//Do not register it here, we do that later
if(tracking_current==NULL) {
state = error;
} else {
state = detecting; //Start with the detecting state
}
}
//Callback for when the screen is left/unloaded
static void leave(void* screen) {
//Remove buttons and checkbox
gui_button_remove(&b_back);
gui_button_remove(&b_select);
gui_checkbox_remove(&c_frame_toggle);
if(state==selecting) { //the user left the screen in the "selecting" phase
touch_unregister_area(&a_area); //remove the touch area
}
if(state==tracking) { //the user left the screen in the "tracking" phase
tracking_current->stop(tracking_current); //stop tracking
pixy_led_set_RGB(0,0,0);
}
}
//Callback for when the screen should be updated
//This is the main loop of the screen. This method will be called repeatedly
static void update(void* screen) {
switch(state) {
case detecting: //Detecting State: Where we try to connect to the pixy
if(pixy_init()==0) { //Pixy connection ok
state = init; //Go to next state
}
break;
case init: //Init State: Where we start the tracking
tracking_current->start(tracking_current);
state=tracking;
break;
case tracking: //Tracking state: Where we render the frame and the tracked objects
pixy_service(); //Receive events (e.g. block-data) from pixy
if(pixy_blocks_are_new()) { //There are new blocks available
if(frame_visible) { //If the user want's us to draw the video data
pixy_render_full_frame(FRAME_START_X,FRAME_START_Y);
} else { //the user want's a colored background
tft_fill_rectangle(FRAME_START_X,FRAME_START_Y,FRAME_END_X,FRAME_END_Y,RGB(200,200,200));
}
#define BLOCK_BUFFER_SIZE 5 //The maximum amount of blocks that we want to receive
struct Block blocks[BLOCK_BUFFER_SIZE]; //Storage to receive blocks from pixy
int blocks_received= pixy_get_blocks(BLOCK_BUFFER_SIZE,blocks); //Try to receive up to BLOCK_BUFFER_SIZE Blocks from pixy
if(blocks_received>=0) { //block receiving ok
tracking_current->update(tracking_current,blocks,blocks_received); //apply tracking
//Draw blocks
for(int i=0; i<blocks_received; i++) { //for each received block
struct Block* block = &(blocks[i]);
//block.x and block.y are the center coordinates of the object relative to the camera origin.
uint16_t x = block->x-1+FRAME_START_X -block->width/2; //Calculate x-Coordinate on the display
uint16_t y = block->y-1+FRAME_START_Y -block->height/2; //Calculate y-Coordinate on the display
tft_draw_rectangle(x,y,x+block->width-1, y+block->height-1,WHITE); //Draw a white rectangle
}
}
}
break;
case preselecting: //Pre-Selecting State: Where we set up the color region selection
{
tracking_current->stop(tracking_current); //Stop tracking
pixy_render_full_frame(FRAME_START_X,FRAME_START_Y); //Render one frame
touch_register_area(&a_area); //Register touch area and receive events from now on
point1_valid=false; //we start with an invalid point1
b_select.text="Abort"; //Change the button text to "Abort"
gui_button_redraw(&b_select); //redraw button
state = selecting; //The user can now select a region
}
break;
case selected: //Selected State: Where we send the users selection to pixy
{
//Ensure that (x1,y1) represent the top-left point and (x2,y2) the bottom-right.
unsigned int tmp;
if(point1.x > point2.x){
tmp = point1.x;
point1.x = point2.x;
point2.x = tmp;
}
if(point1.y > point2.y){
tmp = point1.y;
point1.y = point2.y;
point2.y = tmp;
}
//Send pixy the selected region
pixy_cc_set_region(1,point1.x,point1.y,point2.x-point1.x,point2.y-point1.y);
}
//no break here: We want the following code to be executed as well
case abortselecting: //Abort-Selecting State: Where we deinitialize the stuff we used for region selection
{
touch_unregister_area(&a_area); //Remove the touch area. We'll no longer receive touch events
b_select.text="Select Color"; //Change the button text back to "Select Color"
gui_button_redraw(&b_select); //redraw button
tracking_current->start(tracking_current); //Start tracking again
state=tracking;
}
break;
case selecting: //Selecting State: Where we wait on the user to select a color region
pixy_service(); //receive pixy events
//wait on user to select the image area
break;
case error: //Error State: Where we show an error message and leave the user no other choice than to click the backbutton
//wait on user to click the back button
break;
}
}
//Declare screen callbacks
static SCREEN_STRUCT screen = {
enter,
leave,
update
};
SCREEN_STRUCT* get_screen_tracking() {
return &screen;
}

View File

@@ -0,0 +1,37 @@
#include "screen.h"
/**
* @addtogroup screens
*/
/*@{*/
/**
* @defgroup tracking Tracking (Screen)
* The Tracking-Screen shows the object-tracking and allows some configuration
*/
/*@{*/
/**
* Enum which contains the available tracking implementations
*/
enum Tracking_Implementation {
OUR_TRACKING, //!< Our own tracking PID implementation
REFERENCE_TRACKING//!< Pixy's internal tracking implementation
};
/**
* Sets the current Mode/Tracking Implementation. Call this before using the screen obtained by get_screen_tracking()
* @param impl The new mode
*/
void tracking_set_mode(enum Tracking_Implementation impl);
/**
* Returns a pointer to the tracking screen
* \sa gui_screen_navigate
* @return
*/
SCREEN_STRUCT* get_screen_tracking();
/*@}*/
/*@}*/

View File

@@ -10,7 +10,7 @@ DIRECTORY_STRUCT* filesystem_dir_open(const char* path) {
}
void filesystem_dir_close(DIRECTORY_STRUCT* dir) {
filesystem_dir_close(dir);
ll_filesystem_dir_close(dir);
}
FILE_HANDLE* filesystem_file_open(const char* filename) {

View File

@@ -4,68 +4,144 @@
#include <stdbool.h>
#include <stdint.h>
/**
* @defgroup filesystem Filesystem
* The Filesystem Module provides access to files and directories of a the native filesystem.
*/
/*@{*/
/**
* File Attributes used by implementation
* See http://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#attributes for detailed description
*/
typedef enum {
F_DIR=1,
F_RDO=2,
F_HID=4,
F_SYS=8,
F_ARC=16
F_RDO=0x01,//!< File is readonly. You cannot write to it
F_HID=0x02,//!< File is hidden
F_SYS=0x04,//!< File is a system file
F_DIR=0x10,//!< It's a directory and not a file
F_ARC=0x20 //!< File has the archive flag set (probably unused)
} FILE_ATTRIBUTES;
/**
* Structure which represents last modified date of a file / directory
*/
typedef struct {
unsigned year : 7; //year from 1980 (0..127)
unsigned month: 4; //month (1..12)
unsigned day: 5; //day (1..31)
unsigned year : 7; //!< year from 1980 (0..127)
unsigned month: 4; //!< month (1..12)
unsigned day: 5; //!< day (1..31)
} FILE_DATE_STRUCT;
/**
* Structure which represents last modified time of a file / directory
*/
typedef struct {
unsigned hour : 5; //hour (0..23)
unsigned min: 6; //minute (0..59
unsigned sec: 5; //second/2 (0..29)
unsigned hour : 5; //!< hour (0..23)
unsigned min: 6; //!< minute (0..59
unsigned sec: 5; //!< second/2 (0..29)
} FILE_TIME_STRUCT;
/**
* Structure which represents a file/directory entry. \sa DIRECTORY_STRUCT
*/
typedef struct {
uint32_t fsize; /* File size */
FILE_DATE_STRUCT fdate; /* Last modified date */
FILE_TIME_STRUCT ftime; /* Last modified time */
uint8_t fattrib; /* Attribute */
char* fname; /* File name */
uint32_t fsize; //!< File size in bytes. 0 for directories
FILE_DATE_STRUCT fdate; //!< Last modified date
FILE_TIME_STRUCT ftime; //!< Last modified time
uint8_t fattrib; //!< File/Directory Attributes
char* fname; //!< File/Directory name
} FILE_STRUCT;
/**
* Structure which represents an open directory with all it's entries. \sa filesystem_dir_open
*/
typedef struct {
const char* path;
uint16_t num_files;
FILE_STRUCT* files;
const char* path; //!< Directory path (absolute)
uint16_t num_files; //!< Number of files/directories in this directory
FILE_STRUCT* files; //!< An array with \ref num_files FILE_STRUCT entries
} DIRECTORY_STRUCT;
/**
* Structure which represents an open file. \sa filesystem_file_open
*/
typedef struct {
const char* fname;
uint32_t fpos;
uint32_t fsize;
const char* fname; //!< The absolute file name
uint32_t fpos; //!< The current byte-position in the file. \sa filesystem_file_seek
uint32_t fsize; //!< The total file size in bytes
} FILE_HANDLE;
/**
* Enum to represent the success or error-code of the filesystem_file_* functions
*/
typedef enum {
F_OK,
F_EOF,
F_EACCESS,
F_INVALIDPARAM,
F_DISKERROR
F_OK, //!< Everything ok
F_EOF, //!< The write/read operation tried to write/read past the end of the file. This is not a fatal error.
F_EACCESS, //!< The file can not be read/written due to access problems. This is a fatal error.
F_INVALIDPARAM,//!< You passed invalid parameters to the function
F_DISKERROR //!< A lowlevel disk-error occoured. This is a fatal error.
} FILE_STATUS;
/**
* Initializes the filesystem.
* Call this method before using any filesystem_* functions
* @return true on success
*/
bool filesystem_init();
/**
* Opens a directory and returns a structure which contains all files/subdirectories. \sa filesystem_dir_close()
* @param path The absolute path to the directory to open/read
* @return A Pointer to an initialized DIRECTORY_STRUCT on success, NULL on error
*/
DIRECTORY_STRUCT* filesystem_dir_open(const char* path);
/**
* Closes a previously opened directory. Free's all allocated resources.
* @param dir A Pointer to a DIRECTORY_STRUCT obtained by filesystem_dir_open().
*/
void filesystem_dir_close(DIRECTORY_STRUCT* dir);
/**
* Opens a file for read/writing. \note Depending on the implementation you may only open one file at a time
* @param filename The absolute file path
* @return A Pointer to a FILE_HANDLE on success, NULL on error.
*/
FILE_HANDLE* filesystem_file_open(const char* filename);
/**
* Closes a file.
* @param handle The FILE_HANDLE obtained by filesystem_file_open()
*/
void filesystem_file_close(FILE_HANDLE* handle);
/**
* Set's the read/write position to a new position
* @param handle The FILE_HANDLE obtained by filesystem_file_open()
* @param offset The new read/write position in bytes (absolute).
* @return \ref F_OK on success, an error Code otherwise.
*/
FILE_STATUS filesystem_file_seek(FILE_HANDLE* handle, uint32_t offset);
/**
* Reads some bytes from an open file.
* @param handle The FILE_HANDLE obtained by filesystem_file_open()
* @param buf The Buffer to write the bytes to
* @param size The number of bytes to read
* @return \ref F_OK on success, \ref F_EOF if less than \p size bytes could be read, an error Code otherwise.
*/
FILE_STATUS filesystem_file_read(FILE_HANDLE* handle, uint8_t* buf, uint32_t size);
/**
* Writes some bytes to a open file.
* \note Depending on the implementation the file may not be shrinked or expanded.
* @param handle The FILE_HANDLE obtained by filesystem_file_open()
* @param buf The Buffer to take the bytes from
* @param size The number of bytes to write
* @return \ref F_OK on success, \ref F_EOF if less than \p size bytes could be written, an error Code otherwise.
*/
FILE_STATUS filesystem_file_write(FILE_HANDLE* handle, uint8_t* buf, uint32_t size);
/*@}*/
#endif /* FILESYSTEM_H */

View File

@@ -3,187 +3,152 @@
#include "button.h"
#include <string.h>
#define BRIGHTNESS_VAL 3 //How much the Brightness is in/decreased for button shadows (3 -> Add 1/3 off Full Value)
/* The Idea is as follows:
* When the user add's a button we create a touch area for that region and wait for PEN_DOWN events.
* Once the user puts the pen down in this area we'll redraw the button with different shadows (feedback)
* and we'll now wait on PEN_UP or PEN_LEAVE events.
* If the user takes the pen away while in the area (PEN_UP), we call the provided user callback
* Otherwise (PEN_LEAVE) we only restore the initial shadows
*/
/* Possible improvements:
* Move the button by 1 pixel while he is pressed, to create a "full 3d" experience
* Add events for the case when the button is pressed for a long time, without release
*/
//Method to calculate the shadow colors used to create the "3d" effect
void calculate_shadows(uint16_t bgcolor, uint16_t* light_shadow, uint16_t* dark_shadow) {
#define BRIGHTNESS_VAL 3 //How much the Brightness is in/decreased for button shadows (3 -> Add/Subtract 1/3 off Full Value)
uint16_t c_light,c_dark; //c_light and c_dark will be filled with a lighter and a darker color as the background color (for the shadows)
uint8_t r,g,b;
//separate the channels of the 16-bit rgb565 color
r=(bgcolor&0xF800)>>11;
g=(bgcolor&0x07E0)>>5;
b=(bgcolor&0x001F)>>0;
//For the light shadow color:
if((r + 0x1F/BRIGHTNESS_VAL) > 0x1F) //Adding one third would exceed the maximum of the red channel
c_light=0xF800; //Use full red
else //adding one third to the red channel is fine
c_light=(r+0x1F/BRIGHTNESS_VAL)<<11; //Use same red as in the background, but add one third
if((g + 0x3F/BRIGHTNESS_VAL) > 0x3F) //same for the green channel
c_light|=0x07E0;
else
c_light|=(g+0x3F/BRIGHTNESS_VAL)<<5;
if((b + 0x1F/BRIGHTNESS_VAL) > 0x1F) //and the blue channel
c_light|=0x0018;
else
c_light|=(b+0x1F/BRIGHTNESS_VAL)<<0;
//For the dark shadow color
if(r > (0x1F/BRIGHTNESS_VAL)) //Subtracting one third would NOT exceed the minimum of the red channel
c_dark=(r-0x1F/BRIGHTNESS_VAL)<<11; //Use same red as in the background, but subtract one third
else //Subtracting one third would give us a number below zero
c_dark=0x0000; //use no red channel
if(g > (0x3F/BRIGHTNESS_VAL)) //Same for the green channel
c_dark|=(g-0x3F/BRIGHTNESS_VAL)<<5;
if(b > (0x1F/BRIGHTNESS_VAL)) //and the blue channel
c_dark|=(b-0x1F/BRIGHTNESS_VAL)<<0;
//Assign the calculated shadows to out parameters
if(light_shadow!=NULL) *light_shadow = c_light;
if(dark_shadow!=NULL) *dark_shadow = c_dark;
}
//Callback which is called when the user touches the touch-area we created for the button
void buttons_cb(void* touchArea, TOUCH_ACTION triggeredAction)
//Method shared between normal Buttons and Bitmap Buttons-> Look at comment in headerfile for explanation.
{
TOUCH_AREA_STRUCT * area = (TOUCH_AREA_STRUCT*)touchArea;
BUTTON_STRUCT* button = (BUTTON_STRUCT*)touchArea;
unsigned int c1,c2;
unsigned char r,g,b;
r=(button->bgcolor&0xF800)>>11;
g=(button->bgcolor&0x07E0)>>5;
b=(button->bgcolor&0x001F)>>0;
if((r + 0x1F/BRIGHTNESS_VAL) >0x1F)
c1=0xF800;
else
c1=(r+0x1F/BRIGHTNESS_VAL)<<11;
if((g + 0x3F/BRIGHTNESS_VAL) >0x3F)
c1|=0x07E0;
else
c1|=(g+0x3F/BRIGHTNESS_VAL)<<5;
if((b + 0x1F/BRIGHTNESS_VAL) >0x1F)
c1|=0x0018;
else
c1|=(b+0x1F/BRIGHTNESS_VAL)<<0;
if(r > (0x1F/BRIGHTNESS_VAL))
c2=(r-0x1F/BRIGHTNESS_VAL)<<11;
else
c2=0x0000;
if(g > (0x3F/BRIGHTNESS_VAL))
c2|=(g-0x3F/BRIGHTNESS_VAL)<<5;
if(b > (0x1F/BRIGHTNESS_VAL))
c2|=(b-0x1F/BRIGHTNESS_VAL)<<0;
uint16_t c_light,c_dark; //c_light and c_dark will be filled with a lighter and a darker color as the background color (for the shadows)
calculate_shadows(button->bgcolor,&c_light,&c_dark);
switch(triggeredAction)
{
case PEN_DOWN:
area->hookedActions=PEN_UP|PEN_LEAVE;
tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c2); //Nord
tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c2);//West
tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c1); //S<EFBFBD>d
tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c1); //Ost
case PEN_DOWN: //If the user touches the area for the "first time"
area->hookedActions=PEN_UP|PEN_LEAVE; //for the future we only want PEN_UP and PEN_LEAVE events
//Draw shadows
tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c_dark); //North
tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c_dark);//West
tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c_light); //South
tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c_light); //East
break;
case PEN_UP:
case PEN_LEAVE:
area->hookedActions=PEN_DOWN;
tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c1); //Nord
tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c1);//West
tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c2); //S<EFBFBD>d
tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c2); //Ost
if(triggeredAction==PEN_UP && button->callback!=NULL)
button->callback(button);
case PEN_UP: //If the user took the pen away, while in the area (=button pressed!)
case PEN_LEAVE: //or the user "slided out" of the area
area->hookedActions=PEN_DOWN; //for the future we only want PEN_DOWN events
//Draw inverse shadows
tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c_light); //North
tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c_light);//West
tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c_dark); //South
tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c_dark); //East
if(triggeredAction==PEN_UP && button->callback!=NULL) //If the button got "pressed" instead of left, and the user provided a callback
button->callback(button); //execute the user callback
break;
default:break;
}
}
bool gui_button_add(BUTTON_STRUCT* button)//Registers a button (fill Struct first). Return false if no more Space in the Pointertable (-->Change NUM_AREAS).
bool gui_button_add(BUTTON_STRUCT* button)
{
if(touch_have_empty(1))
if(touch_have_empty(1)) //Check if the touch module can handle one additional area
{
//Calculate width and height of the button text
unsigned int strwidth=tft_font_width(button->font)*strlen(button->text);
unsigned char strheight=tft_font_height(button->font);
button->base.hookedActions=PEN_DOWN;
button->base.callback = buttons_cb;
if(button->base.x2==AUTO)
button->base.hookedActions=PEN_DOWN; //At first we are interested in PEN_DOWN events
button->base.callback = buttons_cb; //Use our own callback for the touch area events
if(button->base.x2==AUTO) { //The user wants us to calculate the button width automatically
//Use string width + half of a character width as button width
button->base.x2= button->base.x1 -1 + strwidth+(tft_font_width(button->font)/2);
else if((button->base.x2-button->base.x1+1)<(strwidth+2))
return false;
} else if((button->base.x2-button->base.x1+1)<(strwidth+2)) { //the provided width is too small to fit the entire text
return false; //report error
}
if(button->base.y2==AUTO)
if(button->base.y2==AUTO) { //The user wants us to calculate the button height automatically
//Use one and a half character heights as button height
button->base.y2=button->base.y1 -1 +strheight+(strheight/2);
else if((button->base.y2-button->base.y1+1)<(strheight+2))
} else if((button->base.y2-button->base.y1+1)<(strheight+2)) { //the provided height is too small to fit the text
return false;
gui_button_redraw(button);
return touch_register_area(&button->base);
}
gui_button_redraw(button); //call the redraw method, which will take care of drawing the entire button
return touch_register_area(&button->base); //Register the touch area and receive events for this button, from now on
}
return false;
return false; //no more touch areas left
}
void gui_button_redraw(BUTTON_STRUCT* button)
{
unsigned int strwidth=tft_font_width(button->font)*strlen(button->text);
unsigned char strheight=tft_font_height(button->font);
unsigned char r,g,b;
unsigned int c;
r=(button->bgcolor&0xF800)>>11;
g=(button->bgcolor&0x07E0)>>5;
b=(button->bgcolor&0x001F)>>0;
//Calculate text dimensions and shadow colors
unsigned int strwidth=tft_font_width(button->font)*strlen(button->text);
unsigned char strheight=tft_font_height(button->font);
uint16_t c_light,c_dark;
calculate_shadows(button->bgcolor,&c_light,&c_dark);
//Draw the background and the 4 lines (shadow colors)
tft_fill_rectangle(button->base.x1+1,button->base.y1+1,button->base.x2-1,button->base.y2-1,button->bgcolor);
if((r + 0x1F/BRIGHTNESS_VAL) >0x1F)
c=0xF800;
else
c=(r+0x1F/BRIGHTNESS_VAL)<<11;
if((g + 0x3F/BRIGHTNESS_VAL) >0x3F)
c|=0x07E0;
else
c|=(g+0x3F/BRIGHTNESS_VAL)<<5;
if((b + 0x1F/BRIGHTNESS_VAL) >0x1F)
c|=0x0018;
else
c|=(b+0x1F/BRIGHTNESS_VAL)<<0;
tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c); //Nord
tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c);//West
if(r > (0x1F/BRIGHTNESS_VAL))
c=(r-0x1F/BRIGHTNESS_VAL)<<11;
else
c=0x0000;
if(g > (0x3F/BRIGHTNESS_VAL))
c|=(g-0x3F/BRIGHTNESS_VAL)<<5;
if(b > (0x1F/BRIGHTNESS_VAL))
c|=(b-0x1F/BRIGHTNESS_VAL)<<0;
tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c); //S<>d
tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c); //Ost
tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c_light); //North
tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c_light);//West
tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c_dark); //South
tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c_dark); //East
//Draw the text
tft_print_line(button->base.x1+(button->base.x2-button->base.x1+1-strwidth)/2,button->base.y1+(button->base.y2-button->base.y1+1-strheight)/2,button->txtcolor,button->bgcolor,button->font,button->text);
}
void gui_button_remove(BUTTON_STRUCT* button)
{
//We only need to unregister the touch area, as we have not allocated anything else
touch_unregister_area((TOUCH_AREA_STRUCT*)button);
}
/*
bool guiAddBitmapButton (BITMAPBUTTON_STRUCT* button)
{
if(touchHaveEmpty(1))
{
button->base.hookedActions=PEN_DOWN;
button->base.callback = buttons_cb;
if(button->base.x2==AUTO)
button->base.x2= button->base.x1 -1 + button->imgwidth + button->imgwidth/4;
else if((button->base.x2-button->base.x1+1)<(button->imgwidth+2))
return false;
if(button->base.y2==AUTO)
button->base.y2=button->base.y1 -1 +button->imgheight + button->imgheight/4;
else if((button->base.y2-button->base.y1+1)<(button->imgheight+2))
return false;
guiRedrawBitmapButton(button);
return touchRegisterArea(&button->base);
}
return false;
}
void guiRedrawBitmapButton(BITMAPBUTTON_STRUCT* button)
{
unsigned char r,g,b;
unsigned int c;
r=(button->bgcolor&0xF800)>>11;
g=(button->bgcolor&0x07E0)>>5;
b=(button->bgcolor&0x001F)>>0;
tftFillRectangle(button->base.x1+1,button->base.y1+1,button->base.x2-1,button->base.y2-1,button->bgcolor);
if((r + 0x1F/BRIGHTNESS_VAL) >0x1F)
c=0xF800;
else
c=(r+0x1F/BRIGHTNESS_VAL)<<11;
if((g + 0x3F/BRIGHTNESS_VAL) >0x3F)
c|=0x07E0;
else
c|=(g+0x3F/BRIGHTNESS_VAL)<<5;
if((b + 0x1F/BRIGHTNESS_VAL) >0x1F)
c|=0x0018;
else
c|=(b+0x1F/BRIGHTNESS_VAL)<<0;
tft_draw_line(button->base.x1+1,button->base.y1,button->base.x2-1,button->base.y1,c); //Nord
tft_draw_line(button->base.x1,button->base.y1+1,button->base.x1,button->base.y2-1,c);//West
if(r > (0x1F/BRIGHTNESS_VAL))
c=(r-0x1F/BRIGHTNESS_VAL)<<11;
else
c=0x0000;
if(g > (0x3F/BRIGHTNESS_VAL))
c|=(g-0x3F/BRIGHTNESS_VAL)<<5;
if(b > (0x1F/BRIGHTNESS_VAL))
c|=(b-0x1F/BRIGHTNESS_VAL)<<0;
tft_draw_line(button->base.x1+1,button->base.y2,button->base.x2-1,button->base.y2,c); //S<>d
tft_draw_line(button->base.x2,button->base.y1+1,button->base.x2,button->base.y2-1,c); //Ost
tftDrawBitmapUnscaledStreamedRaw(button->base.x1+(button->base.x2-button->base.x1+1-button->imgwidth)/2,button->base.y1+(button->base.y2-button->base.y1+1-button->imgheight)/2,button->imgwidth,button->imgheight,button->filename);
}
void guiRemoveBitmapButton(BITMAPBUTTON_STRUCT* button)
{
touchUnregisterArea((TOUCH_AREA_STRUCT*)button);
}
*/

View File

@@ -1,6 +1,8 @@
#ifndef BUTTON_H
#define BUTTON_H
#include "touch.h"
/**
* @defgroup gui Gui
* The Gui Module
@@ -9,16 +11,21 @@
/**
* @defgroup button Button
* The Button Gui-Element
* The Button Gui-Element is a clickable, rectangular box with a label inside.
* When it is pressed and released you will be notified via the provided callback.
*/
/*@}*/
/**
* @addtogroup button
*/
/*@{*/
#include "touch.h"
/**
* Prototype for Event Listeners (called when the button is pressed)
* \note You should NOT execute long running things in this callback nor should you update the gui. But you can call gui_screen_navigate() for instance.
* @param button The pointer to the BUTTON_STRUCT where to corresponding Button was pressed
*/
typedef void (*BUTTON_CALLBACK)(void *button);
@@ -30,7 +37,7 @@ typedef void (*BUTTON_CALLBACK)(void *button);
typedef struct {
TOUCH_AREA_STRUCT base; //!< Basic geometry of the button. You only need to set the x1, y1, x2, y2 members of this struct.
uint16_t bgcolor; //!< The 16-bit background color of the button
BUTTON_CALLBACK callback; //!< Callback
BUTTON_CALLBACK callback; //!< Callback which is executed when the button is pressed
uint16_t txtcolor; //!< The 16-bit text color
uint8_t font; //!< The number of the font to use
const char *text; //!< The label of the button
@@ -58,27 +65,7 @@ void gui_button_remove(BUTTON_STRUCT* button);
*/
void gui_button_redraw(BUTTON_STRUCT* button);
/*
bool guiAddBitmapButton(BITMAPBUTTON_STRUCT* button);
void guiRemoveBitmapButton(BITMAPBUTTON_STRUCT* button);
void guiRedrawBitmapButton(BITMAPBUTTON_STRUCT* button);
*/
/*
typedef struct {
TOUCH_AREA_STRUCT base;
unsigned int bgcolor;
BUTTON_CALLBACK callback; //Callback
unsigned char imgwidth;
unsigned char imgheight;
char* filename;
} BITMAPBUTTON_STRUCT;
*/
//Notice that the first 3 Members are Equal, so it's possible to cast it to a BUTTON_STRUCT even if it's a BITMAPBUTTON_STRUCT (when changeing only the first 3 Members).
/*@}@}*/
/*@}*/
#endif /* BUTTON_H */

View File

@@ -3,29 +3,44 @@
#include "checkbox.h"
#include <stdio.h>
#define BRIGHTNESS_VAL 2 //How much the Brightness is in/decreased for checkbox shadows (3 -> Add 1/3 off Full Value)
#define ACTIVE_COLOR RGB(251,208,123)
#define BORDER_COLOR RGB(29,82,129)
#define BACKGROUND_COLOR WHITE
/* The idea is as follows:
* When the user creates a checkbox we create a touch area for that region and wait for PEN_DOWN events.
* Once the user puts the pen down in this area we'll redraw the checkbox with different shadows (feedback)
* and we'll now wait on PEN_UP or PEN_LEAVE events.
* If the user takes the pen away while in the area (PEN_UP), we toggle the checkbox and we call the provided user callback
* Otherwise (PEN_LEAVE) we only restore the initial shadows
*/
#define ACTIVE_COLOR RGB(251,208,123) //shadow color (inside of border)
#define BORDER_COLOR RGB(29,82,129) //1px border color
#define BACKGROUND_COLOR WHITE //Background color
//Callback which is called when the user touches the touch-area we created for the checkbox
void checkboxes_cb(void* touchArea, TOUCH_ACTION triggeredAction)
{
TOUCH_AREA_STRUCT * area = (TOUCH_AREA_STRUCT*)touchArea;
CHECKBOX_STRUCT* checkbox = (CHECKBOX_STRUCT*)touchArea;
switch(triggeredAction)
{
case PEN_DOWN:
area->hookedActions=PEN_UP|PEN_LEAVE;
case PEN_DOWN: //If the user touches the area for the "first time"
area->hookedActions=PEN_UP|PEN_LEAVE; //for the future we only want PEN_UP and PEN_LEAVE events
//Draw active shadows
tft_draw_rectangle(checkbox->base.x1+1,checkbox->base.y1+1,checkbox->base.x2-1,checkbox->base.y2-1,ACTIVE_COLOR);
tft_draw_rectangle(checkbox->base.x1+2,checkbox->base.y1+2,checkbox->base.x2-2,checkbox->base.y2-2,ACTIVE_COLOR);
break;
case PEN_UP:
checkbox->checked=!checkbox->checked;
gui_checkbox_update(checkbox);
if(checkbox->callback!=NULL)
checkbox->callback(checkbox,checkbox->checked);
case PEN_LEAVE:
area->hookedActions=PEN_DOWN;
case PEN_UP: //If the user took the pen away, while in the area (=toggle checkbox!)
checkbox->checked=!checkbox->checked; //Toggle checkbox state
gui_checkbox_update(checkbox); //redraw/overdraw tickmark
if(checkbox->callback!=NULL) { //The user provided a callback
checkbox->callback(checkbox,checkbox->checked); //Call the provided callback with the new checked state
}
// no break statement here!
case PEN_LEAVE: //if the user "slided out" of the area
area->hookedActions=PEN_DOWN; //for the future we only want PEN_DOWN events
//Draw inactive shadows
tft_draw_rectangle(checkbox->base.x1+1,checkbox->base.y1+1,checkbox->base.x2-1,checkbox->base.y2-1,BACKGROUND_COLOR);
tft_draw_rectangle(checkbox->base.x1+2,checkbox->base.y1+2,checkbox->base.x2-2,checkbox->base.y2-2,BACKGROUND_COLOR);
break;
@@ -35,48 +50,66 @@ void checkboxes_cb(void* touchArea, TOUCH_ACTION triggeredAction)
bool gui_checkbox_add(CHECKBOX_STRUCT* checkbox)
{
if(touch_have_empty(1))
if(touch_have_empty(1)) //Check if the touch module can handle one additional area
{
unsigned char size=0;
checkbox->base.hookedActions=PEN_DOWN;
checkbox->base.callback = checkboxes_cb;
checkbox->base.hookedActions=PEN_DOWN; //At first we are interested in PEN_DOWN events
checkbox->base.callback = checkboxes_cb; //Use our own callback for the touch area events
//Check the size of the checkbox
if(checkbox->base.x2>checkbox->base.x1)
size = checkbox->base.x2 - checkbox->base.x1;
size = checkbox->base.x2 - checkbox->base.x1; //use width a as size
if(checkbox->base.y2>checkbox->base.y1)
{
if((checkbox->base.y2 - checkbox->base.y1)>size)
size = checkbox->base.y2 - checkbox->base.y1;
if((checkbox->base.y2 - checkbox->base.y1)>size) //height is larger than size
size = checkbox->base.y2 - checkbox->base.y1; //use height as size
}
if((size&0x01))
size++;
if(size==0) { //no size found (maybe swap x2 and x1 or y2 and y1 ?)
return false; //signal error
}
if((size&0x01)) //the size is an odd number
size++; //make size an even number
//Correct x2,y2 so that the checkbox is quadratic
checkbox->base.x2 = checkbox->base.x1 + size;
checkbox->base.y2 = checkbox->base.y1 + size;
gui_checkbox_redraw(checkbox);
return touch_register_area(&checkbox->base);
gui_checkbox_redraw(checkbox);//Call redraw method, which will take care of the drawing of the entire checkbox
return touch_register_area(&checkbox->base); //Register the touch area and receive events for this checkbox, from now on
}
return false;
return false; //no more touch areas left
}
void gui_checkbox_redraw(CHECKBOX_STRUCT* checkbox)
{
//Draw background and border
tft_fill_rectangle(checkbox->base.x1+1,checkbox->base.y1+1,checkbox->base.x2-1,checkbox->base.y2-1,BACKGROUND_COLOR);
tft_draw_rectangle(checkbox->base.x1,checkbox->base.y1,checkbox->base.x2,checkbox->base.y2,BORDER_COLOR);
if(checkbox->checked)
gui_checkbox_update(checkbox);
if(checkbox->checked) { //checkbox is currently checked
gui_checkbox_update(checkbox); //Call update method which will draw the tickmark
}
}
void gui_checkbox_remove(CHECKBOX_STRUCT* checkbox)
{
//We only need to unregister the touch area, as we have not allocated anything else
touch_unregister_area((TOUCH_AREA_STRUCT*)checkbox);
}
void gui_checkbox_update(CHECKBOX_STRUCT* checkbox)
{
unsigned int c = (checkbox->checked)? checkbox->fgcolor:BACKGROUND_COLOR;
unsigned int xcent = checkbox->base.x1+(checkbox->base.x2-checkbox->base.x1)*6/14;
unsigned int c = (checkbox->checked)? checkbox->fgcolor:BACKGROUND_COLOR; //color to use for the tickmark
//helper points inside the checkbox
unsigned int xcent = checkbox->base.x1+(checkbox->base.x2-checkbox->base.x1)*6/14;
unsigned int yleft = checkbox->base.y2 - (xcent- checkbox->base.x1) - 1 ;
unsigned int yright = checkbox->base.y2 - (checkbox->base.x2 - xcent) - 1 ;
unsigned int ybot = checkbox->base.y2 - 4;
//Draw tickmark as a 3pixel wide line
tft_draw_line(checkbox->base.x1+3,yleft-1,xcent,ybot -1,c);
tft_draw_line(checkbox->base.x1+3,yleft,xcent,ybot ,c);
tft_draw_line(checkbox->base.x1+3,yleft+1,xcent,ybot + 1,c);

View File

@@ -1,6 +1,8 @@
#ifndef CHECKBOX_H
#define CHECKBOX_H
#include "touch.h"
/**
* @addtogroup gui
*/
@@ -8,26 +10,61 @@
/**
* @defgroup checkbox Checkbox
* The Checkbox Gui-Element
* The Checkbox Gui-Element is a clickable, rectangular box with an optional tickmark inside of it.
* When the checkbox is pressed and released it's tick state changes and you will be notified via the provided callback.
*/
/*@}*/
/**
* @addtogroup checkbox
*/
/*@{*/
/**
* Prototype for Event Listeners (called when the checkbox state has changed)
* \note You should NOT execute long running things in this callback nor should you update the gui. But you can call gui_screen_navigate() for instance.
* @param checkbox The pointer to the CHECKBOX_STRUCT where to corresponding Checkbox has changed the state
* @param checked A boolean which indicates whether the checkbox is now checked or not.
*/
typedef void (*CHECKBOX_CALLBACK)(void *checkbox, bool checked);
typedef void (*CHECKBOX_CALLBACK)(void *checkbox, bool checked); //!< Function pointer used...
/**
* Structure to configure the Checkbox
*/
typedef struct {
TOUCH_AREA_STRUCT base;
uint16_t fgcolor;
bool checked;
CHECKBOX_CALLBACK callback; //Callback
TOUCH_AREA_STRUCT base; //!< Basic geometry of the Checkbox. You only need to set the x1, y1, x2, y2 members of this struct.
uint16_t fgcolor; //!< The 16-bit color of the tickmark
bool checked; //!< A boolean which indicates whether or not the checkbox is currently checked.
CHECKBOX_CALLBACK callback; //!< Callback which is executed when the checkbox changes state
} CHECKBOX_STRUCT;
/**
* Adds a checkbox. Your Callback will be called from now on, if the checkbox changes state
* @param checkbox A Pointer to the preinitialized CHECKBOX_STRUCT
* @return true on success
*/
bool gui_checkbox_add(CHECKBOX_STRUCT* checkbox);
/**
* Removes the checkbox. You will no longer receive events for this checkbox. This function will not overdraw the region where the checkbox was located.
* @param checkbox A Pointer to the CHECKBOX_STRUCT
*/
void gui_checkbox_remove(CHECKBOX_STRUCT* checkbox);
/**
* Updates the checkbox. Call this function when you change the state of the checkbox through code.
* @param checkbox A Pointer to the CHECKBOX_STRUCT
*/
void gui_checkbox_update(CHECKBOX_STRUCT* checkbox);
/**
* Redraws the checkbox. Call this method if you have to redraw the entire screen or if you want to draw a checkbox on top of an image.
* @param checkbox A Pointer to the CHECKBOX_STRUCT
*/
void gui_checkbox_redraw(CHECKBOX_STRUCT* checkbox);
#define CHECKBOX_WIN_FG_COLOR RGB(32,161,34)
/*@}@}*/
/*@}*/
#endif /* CHECKBOX_H */

View File

@@ -6,39 +6,51 @@
#include <stddef.h> //for offsetof macro
#include <stdlib.h> //for abs
/* The idea is as follows:
* When the user add's a numupdown we create two buttons, one with a plus and one with a minus sign in it
* When the user presses one of the buttons we check and increase the value and execute the provided user callback
*/
#define BASE_COLOR RGB(90,90,90)
#define BASE_COLOR RGB(90,90,90) //Background color for the whole element
//Callback which is called when the user presses the "plus" button
void button_up_cb(void* button)
{
//Get the pointer to the numupdown: subtract the offset of the buttonUp member in the struct from the button pointer
NUMUPDOWN_STRUCT* element = button-offsetof(NUMUPDOWN_STRUCT,buttonUp);
if(element->value<element->max) {
element->value++;
gui_numupdown_update(element);
if(element->callback!=NULL) {
element->callback(element,element->value);
if(element->value<element->max) { //old value lies below the maximum
element->value++; //let's increase the value
gui_numupdown_update(element); //and redraw everything
if(element->callback!=NULL) { //the user provided a callback
element->callback(element,element->value); //Call the user callback with the new value
}
}
}
//Callback which is called when the user presses the "minus" button
void button_down_cb(void* button)
{
//Get the pointer to the numupdown: subtract the offset of the buttonDown member in the struct from the button pointer
NUMUPDOWN_STRUCT* element = button-offsetof(NUMUPDOWN_STRUCT,buttonDown);
if(element->value>element->min) {
element->value--;
gui_numupdown_update(element);
if(element->callback!=NULL) {
element->callback(element,element->value);
if(element->value>element->min) { //old value lies above the minimum
element->value--; //let's decrease the value
gui_numupdown_update(element); //and redraw everything
if(element->callback!=NULL) { //the user provided a callback
element->callback(element,element->value); //Call the user callback with the new value
}
}
}
//Method to calculate the number of characters needed to print the provided number in decimal notation (with optional sign)
static uint8_t calc_text_width(int16_t val) {
uint8_t width = 1 + (val<0); //1 if positive, 2 if negative (to let space for sign)
val=abs(val);
while(val>=10) {
val/=10;
width++;
val=abs(val); //Make the number positive
while(val>=10) { //while we have two or more digits
val/=10; //remove one digit
width++; //add one character
}
return width;
}
@@ -46,24 +58,24 @@ static uint8_t calc_text_width(int16_t val) {
bool gui_numupdown_add(NUMUPDOWN_STRUCT* numupdown)
{
if(touch_have_empty(2)) //We require 2 TouchAreas (for Buttons)
if(touch_have_empty(2)) //Check if the touch module can handle two additional areas
{
if(numupdown->min > numupdown->max) return false;
if(numupdown->value > numupdown->max) {
numupdown->value = numupdown->max;
if(numupdown->min > numupdown->max) { //min is bigger than max?
return false; //invalid parameter
}
if(numupdown->value < numupdown->min) {
numupdown->value = numupdown->min;
} else if(numupdown->value > numupdown->max) {
numupdown->value = numupdown->max;
if(numupdown->value < numupdown->min) { //value is smaller than min?
numupdown->value = numupdown->min; //normalize value
} else if(numupdown->value > numupdown->max) { //value is bigger than max?
numupdown->value = numupdown->max; //normalize value
}
uint8_t tw1 = calc_text_width(numupdown->max);
uint8_t tw2 = calc_text_width(numupdown->min);
if(tw2 > tw1) tw1 = tw2;
uint8_t width= tft_font_width(0)*(tw1+1);
uint8_t tw1 = calc_text_width(numupdown->max); //Calculate character width to render maximum value
uint8_t tw2 = calc_text_width(numupdown->min); //Calculate character width to render minimum value
if(tw2 > tw1) tw1 = tw2; //ensure tw1 contains the larger number of the two
uint8_t width= tft_font_width(0)*(tw1+1); //Calculate width of the number area
//Add "minus" button to the left side of the number area
numupdown->buttonDown.base.x1=numupdown->x;
numupdown->buttonDown.base.y1=numupdown->y;
numupdown->buttonDown.base.x2=AUTO;
@@ -74,6 +86,8 @@ bool gui_numupdown_add(NUMUPDOWN_STRUCT* numupdown)
numupdown->buttonDown.txtcolor=WHITE;
numupdown->buttonDown.callback = button_down_cb;
gui_button_add(&numupdown->buttonDown);
//Add "plus" button to the right side of the number area
numupdown->buttonUp.base.x1=numupdown->buttonDown.base.x2+width+2;
numupdown->buttonUp.base.y1=numupdown->y;
numupdown->buttonUp.base.x2=AUTO;
@@ -85,36 +99,45 @@ bool gui_numupdown_add(NUMUPDOWN_STRUCT* numupdown)
numupdown->buttonUp.callback = button_up_cb;
gui_button_add(&numupdown->buttonUp);
//Draw background and label of the number area
tft_fill_rectangle(numupdown->buttonDown.base.x2+2,numupdown->y,numupdown->buttonDown.base.x2+width,numupdown->buttonUp.base.y2,BASE_COLOR);
tft_print_formatted(numupdown->buttonDown.base.x2+2+tft_font_width(0)/2,numupdown->y+tft_font_height(0)/2,numupdown->fgcolor,BASE_COLOR,0,"%*d",tw1,numupdown->value);
return true;
}
return false;
return false; //not enough touch areas left
}
void gui_numupdown_remove(NUMUPDOWN_STRUCT* numupdown)
{
{
//remove the two buttons, we have no other allocated resources
gui_button_remove(&numupdown->buttonUp);
gui_button_remove(&numupdown->buttonDown);
}
}
void gui_numupdown_redraw(NUMUPDOWN_STRUCT* numupdown)
{
//redraw the two buttons
gui_button_redraw(&numupdown->buttonUp);
gui_button_redraw(&numupdown->buttonDown);
//call update method which will take care of the number-area rendering
gui_numupdown_update(numupdown);
}
void gui_numupdown_update(NUMUPDOWN_STRUCT* numupdown)
{
{
//Calculate the number area width again (see above)
uint8_t tw1 = calc_text_width(numupdown->max);
uint8_t tw2 = calc_text_width(numupdown->min);
if(tw2 > tw1) tw1 = tw2;
uint8_t width= tft_font_width(0)*(tw1+1);
//Draw background and label of the number area
tft_fill_rectangle(numupdown->buttonDown.base.x2+2,numupdown->y,numupdown->buttonDown.base.x2+width,numupdown->buttonUp.base.y2,BASE_COLOR);
tft_print_formatted(numupdown->buttonDown.base.x2+2+tft_font_width(0)/2,numupdown->y+tft_font_height(0)/2,numupdown->fgcolor,BASE_COLOR,0,"%*d",tw1,numupdown->value);
}
}
void gui_numupdown_redraw(NUMUPDOWN_STRUCT* numupdown)
{
gui_button_redraw(&numupdown->buttonUp);
gui_button_redraw(&numupdown->buttonDown);
gui_numupdown_update(numupdown);
}

View File

@@ -1,6 +1,8 @@
#ifndef NUMUPDOWN_H
#define NUMUPDOWN_H
#include "button.h"
/**
* @addtogroup gui
*/
@@ -10,32 +12,62 @@
* @defgroup numupdown NummericUpDown
* The NummericUpDown Gui Element
*/
/*@}*/
/**
* @addtogroup numupdown
*/
/*@{*/
/**
* Prototype for Event Listeners (called when the NummericUpDown value has changed)
* \note You should NOT execute long running things in this callback nor should you update the gui. But you can call gui_screen_navigate() for instance.
* @param numupdown The pointer to the NUMUPDOWN_STRUCT where to corresponding NummericUpDown has changed it's value
* @param value The new value of the NummericUpDown
*/
typedef void (*NUMUPDOWN_CALLBACK)(void *numupdown, int16_t value);
#include "button.h"
typedef void (*NUMUPDOWN_CALLBACK)(void *numupdown, int16_t value); //!< Function pointer used...
/**
* Structure to configure the NummericUpDown
*/
typedef struct {
uint16_t x;
uint16_t y;
uint16_t fgcolor;
int16_t value;
int16_t min;
int16_t max;
NUMUPDOWN_CALLBACK callback; //Callback
uint16_t x; //!< The x-Coordinate of the Top-Left Starting Point.
uint16_t y; //!< The y-Coordinate of the Top-Left Starting Point.
uint16_t fgcolor; //!< The 16-bit color of the value-text
int16_t value; //!< The current/default value
int16_t min; //!< The minimum possible value (inclusive)
int16_t max; //!< The maximum possible value (inclusive)
NUMUPDOWN_CALLBACK callback; //!< Callback which is executed when the value changes
//Internally used:
BUTTON_STRUCT buttonUp;
BUTTON_STRUCT buttonDown;
BUTTON_STRUCT buttonUp; //!< For internal use, don't change, don't initialize
BUTTON_STRUCT buttonDown; //!< For internal use, don't change, don't initialize
} NUMUPDOWN_STRUCT;
/**
* Adds a NummericUpDown. Your Callback will be called from now on, if the numupdown's value changes
* @param numupdown A Pointer to the preinitialized NUMUPDOWN_STRUCT
* @return true on success
*/
bool gui_numupdown_add(NUMUPDOWN_STRUCT* numupdown);
/**
* Removes the NummericUpDown. You will no longer receive events for this numupdown. This function will not overdraw the region where the numupdown was located.
* @param numupdown A Pointer to the NUMUPDOWN_STRUCT
*/
void gui_numupdown_remove(NUMUPDOWN_STRUCT* numupdown);
/**
* Updates the NummericUpDown. Call this function when you change the value/min/max of the numupdown through code.
* @param numupdown A Pointer to the NUMUPDOWN_STRUCT
*/
void gui_numupdown_update(NUMUPDOWN_STRUCT* numupdown);
/**
* Redraws the NummericUpDown. Call this method if you have to redraw the entire screen or if you want to draw a numupdown on top of an image.
* @param numupdown A Pointer to the NUMUPDOWN_STRUCT
*/
void gui_numupdown_redraw(NUMUPDOWN_STRUCT* numupdown);
/*@}@}*/
/*@}*/
#endif /* NUMUPDOWN_H */

View File

@@ -1,32 +1,40 @@
#include "screen.h"
/* The idea is as follows:
* We only call screen callbacks from the gui_screen_update() method, which is called from the applications main loop.
* Instructions to switch the screen will be delayed until the gui_screen_update() method is called again.
* This makes it safe to change the screen from an touch interrupt (e.g. button callback)
*/
static SCREEN_STRUCT* screen_list = NULL;
static SCREEN_STRUCT* screen_current = NULL;
static volatile SCREEN_STRUCT* screen_goto = NULL;
static SCREEN_STRUCT* screen_list = NULL; //Head of the linked list which stores the screen history.
static SCREEN_STRUCT* screen_current = NULL; //Pointer to the current screen (= tail of the list)
static volatile SCREEN_STRUCT* screen_goto = NULL; //Screen we should navigate to once we enter the gui_screen_update() method again
SCREEN_STRUCT* gui_screen_get_current() {
return screen_current;
}
void gui_screen_update() {
if(screen_goto!=NULL) { //we received the task to switch the screen
SCREEN_STRUCT* go = (SCREEN_STRUCT*) screen_goto; //Backup volatile variable
screen_goto=NULL;
if(go->next!=NULL) { //we're going back
if(go->next!=screen_current) return; //list corrupted?
screen_current->on_leave(screen_current);
go->next=NULL;
} else { //we're going forward
screen_goto=NULL; //reset the "goto instruction", since we're processing it now
if(go->next!=NULL) { //The screen is not the last in the list, so we're going back
if(go->next!=screen_current) { //this condition should always be false
return; //list corrupted?
}
screen_current->on_leave(screen_current); //let the current screen free/unregister it's resources
go->next=NULL; //remove the current screen from the list
} else { //we're going forward (to a new screen)
if(screen_current!=NULL) { //this is not the first screen
screen_current->on_leave(screen_current);
screen_current->next = go;
} else { //first screen ever seen
screen_list=go;
}
screen_current->on_leave(screen_current); //let the current screen free/unregister it's resources
screen_current->next = go; //append the new screen to the end of the list
} else { //first screen ever seen
screen_list=go; //set the new screen as list-head
}
}
go->on_enter(go);
screen_current =go;
go->on_enter(go); //let the new screen allocate/register it's resources
screen_current = go; //the new screen is now the current screen. Transition done
}
if(screen_current!=NULL) { //A screen has been set
@@ -37,16 +45,18 @@ void gui_screen_update() {
bool gui_screen_navigate(SCREEN_STRUCT* screen) {
if(screen==NULL) return false;
screen->next = NULL;
screen_goto=screen; //send message to main loop, to switch the screen
if(screen==NULL) { //invalid argument passed
return false;
}
screen->next = NULL; //this will become the new tail of the list, so the next pointer must be NULL
screen_goto=screen; //"send message" to main loop, to switch the screen
return true;
}
bool gui_screen_back() {
if(screen_list==NULL) return false;
if(screen_list==NULL) { //the list head is emtpy, nothing to go back to
return false;
}
SCREEN_STRUCT* current = screen_list;
SCREEN_STRUCT* last = NULL;
//Find second last element in list
@@ -54,15 +64,8 @@ bool gui_screen_back() {
last = current;
current = current->next;
}
if(last==NULL) return false; //There's only a single screen.
if(current!=screen_current) return false; //List corrupted?
screen_goto=last; //send message to main loop, to switch the screen
if(last==NULL) return false; //There's only a single screen, there's no going back here
if(current!=screen_current) return false; //The last entry in the list is not the current screen. List corrupted?
screen_goto=last; //"send message" to main loop, to switch the screen
return true;
}

View File

@@ -1,6 +1,9 @@
#ifndef SCREEN_H
#define SCREEN_H
#include <stdio.h>
#include <stdbool.h>
/**
* @addtogroup gui
*/
@@ -8,38 +11,65 @@
/**
* @defgroup screen Screen
* The Screen Submodule
* The Screen Submodule provides an api to navigate between different "screens" on the UI.
* Each screen must provide an enter, update and a leave method; which will be called from this module at the right time.
* The implemented screens of the application are documented in the \ref screens module.
*/
/*@}*/
/**
* @addtogroup screen
*/
/*@{*/
#include <stdio.h>
#include <stdbool.h>
typedef void (*SCREEN_CALLBACK)(void* screen); //!< Function pointer used...
/**
* Prototype for Event Listeners (called when the screen is entered, left or should be updated)
* @param screen The pointer to the SCREEN_STRUCT where the event occurred
*/
typedef void (*SCREEN_CALLBACK)(void* screen);
/**
* Structure to configure the Screen
*/
typedef struct SCREEN_S{
SCREEN_CALLBACK on_enter;
SCREEN_CALLBACK on_leave;
SCREEN_CALLBACK on_update;
struct SCREEN_S* next; //Used internally. do not modify
SCREEN_CALLBACK on_enter; //!< The Callback which is called when the screen is entered. Add/Register all UI-Elements here
SCREEN_CALLBACK on_leave; //!< The Callback which is called when the screen is left. Remove/Unregister all UI-Elements here
SCREEN_CALLBACK on_update; //!< The Callback which is called repeatedly when the screen should be updated. Update/Redraw all UI-Elements here
struct SCREEN_S* next; //!< Used internally. do not modify, do not initialize
} SCREEN_STRUCT;
//Navigate to the given string as soon as the app enters the main loop again. Method can be called from an interrupt
/**
* Navigate to the given screen as soon as the app enters the main loop again (and gui_screen_update() is called)
* It's safe to call this method from an interrupt
* @param screen A Pointer to the preinitialized SCREEN_STRUCT
* @return true on success
*/
bool gui_screen_navigate(SCREEN_STRUCT* screen);
//Navigate one screen back as soon as the app enters the main loop again. Method can be called from an interrupt
/**
* Navigate one screen back as soon as the app enters the main loop again.
* It's safe to call this method from an interrupt
* @return true on success
*/
bool gui_screen_back();
//Returns the current active screen
/**
* Returns the currently active screen
* @return A Pointer to the active SCREEN_STRUCT
*/
SCREEN_STRUCT* gui_screen_get_current();
//Updates/switches the screens. Call this from the app main loop, as fast as you can.
/**
* Updates the current screen. Switches the screen if gui_screen_navigate() or gui_screen_back() have been called since the last call to this method.
* This method should be called repeatedly from the main loop (e.g. app_process())
*/
void gui_screen_update();
/*@}@}*/
/*@}*/
#endif /* SCREEN_H */

View File

@@ -1,5 +1,22 @@
#include "filesystem.h"
/**
* @addtogroup lowlevel
*/
/*@{*/
/**
* @defgroup ll_filesystem Filesystem (LowLevel)
* Low level functions for the \ref filesystem module
*/
/*@}*/
/**
* @addtogroup ll_filesystem
*/
/*@{*/
bool ll_filesystem_init();
DIRECTORY_STRUCT* ll_filesystem_dir_open(const char* path);
@@ -9,3 +26,5 @@ void ll_filesystem_file_close(FILE_HANDLE* handle);
FILE_STATUS ll_filesystem_file_seek(FILE_HANDLE* handle, uint32_t offset);
FILE_STATUS ll_filesystem_file_read(FILE_HANDLE* handle, uint8_t* buf, uint32_t size);
FILE_STATUS ll_filesystem_file_write(FILE_HANDLE* handle, uint8_t* buf, uint32_t size);
/*@}*/

View File

@@ -1,7 +1,29 @@
#include <stdbool.h>
#include <stdint.h>
/**
* @defgroup lowlevel LowLevel
* The Low-Level platform abstraction layer
*/
/*@{*/
/**
* @defgroup ll_system System (LowLevel)
* Low level functions of the \ref system Module
*/
/*@}*/
/**
* @addtogroup ll_system
*/
/*@{*/
bool ll_system_init();
void ll_system_delay(uint32_t msec);
void ll_system_process();
void ll_system_toggle_led();
/*@}*/

View File

@@ -1,6 +1,23 @@
#include <stdint.h>
#include <stdbool.h>
/**
* @addtogroup lowlevel
*/
/*@{*/
/**
* @defgroup ll_tft TFT (LowLevel)
* Low level functions for the \ref tft module
*/
/*@}*/
/**
* @addtogroup ll_tft
*/
/*@{*/
// init functions
bool ll_tft_init();
@@ -19,6 +36,6 @@ uint8_t ll_tft_font_height(uint8_t fontnum);
uint8_t ll_tft_font_width(uint8_t fontnum);
void ll_tft_draw_char(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, uint8_t font, char c);
/*@}*/

View File

@@ -1,6 +1,23 @@
#include <stdint.h>
#include <stdbool.h>
/**
* @addtogroup lowlevel
*/
/*@{*/
/**
* @defgroup ll_touch Touch (LowLevel)
* Low level functions for the \ref touch module
*/
/*@}*/
/**
* @addtogroup ll_touch
*/
/*@{*/
bool ll_touch_init();
/*@}*/

View File

@@ -17,7 +17,6 @@
#define __PIXY_H__
#include <stdint.h>
#include <unistd.h>
#include "pixydefs.h"
// Pixy C API //
@@ -27,6 +26,13 @@ extern "C"
{
#endif
/**
* @defgroup pixy Pixy
* The Pixy Module
*/
/*@{*/
#define PIXY_MAX_SIGNATURE 7
// Pixy x-y position values
@@ -164,7 +170,7 @@ extern "C"
/**
@brief Enable or disable pixy camera auto white balance.
@param enable 1: Enable white balance.
@param value 1: Enable white balance.
0: Disable white balance.
@return 0 Success
@return Negative Error
@@ -220,7 +226,7 @@ extern "C"
@return 0 Success
@return Negative Error
*/
int pixy_cam_set_exposure_compensation(uint8_t gain, uint16_t compensation);
int pixy_cam_set_exposure_compensation(uint8_t gain, uint16_t comp);
/**
@brief Get pixy camera exposure compensation.
@@ -229,7 +235,7 @@ extern "C"
@return 0 Success
@return Negative Error
*/
int pixy_cam_get_exposure_compensation(uint8_t * gain, uint16_t * compensation);
int pixy_cam_get_exposure_compensation(uint8_t * gain, uint16_t * comp);
/**
@brief Set pixy camera brightness.
@@ -279,6 +285,8 @@ extern "C"
*/
int pixy_get_firmware_version(uint16_t * major, uint16_t * minor, uint16_t * build);
/*@}*/
#ifdef __cplusplus
}
#endif

View File

@@ -1,6 +1,9 @@
#ifndef SYSTEM_H
#define SYSTEM_H
#include <stdbool.h>
#include <stdint.h>
/**
* @defgroup system System
* The System Module provides access to delay functions, leds and provides a system init function
@@ -8,9 +11,6 @@
/*@{*/
#include <stdbool.h>
#include <stdint.h>
/**
* Initializes the system. Call this method at the start of your app_init() function and before using any system_* functions
* @return true on success

View File

@@ -3,6 +3,17 @@
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include "filesystem.h"
/* The idea is as follows:
* Most of the tft_* functions can be forwarded to the lowlevel implementation.
* The exceptions are commented below.
* Make sure to have a look at the doxygen comments for the lowlevel functions and for the tft_* functions
*/
/* Possible improvements:
* For formatted printing implement putchar, instead of writing into a buffer and drawing that buffer afterwards
*/
bool tft_init() {
return ll_tft_init();
@@ -51,19 +62,82 @@ uint8_t tft_font_width(uint8_t fontnum) {
return ll_tft_font_width(fontnum);
}
//Print line can be done with multiple calls to draw_char
void tft_print_line(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, uint8_t font, const char* text) {
if(font>=ll_tft_num_fonts()) return;
for(int i=0; i<strlen(text); i++) {
ll_tft_draw_char(x,y,color,bgcolor, font, text[i]);
x+=ll_tft_font_width(font);
if(font>=ll_tft_num_fonts()) return; //invalid font index
for(int i=0; i<strlen(text); i++) { //for each char in the line
ll_tft_draw_char(x,y,color,bgcolor, font, text[i]); //draw the char
x+=ll_tft_font_width(font); //and increase the x position
}
}
//Printing a formatted line can be done by printing the line in a buffer using "sprintf" and then calling print_line
void tft_print_formatted(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, uint8_t font, const char* format, ...) {
static char buffer[256]; //not sure if that's the best solution. It would propbably better to implement putchar and use vprintf
static char buffer[128]; //buffer to save the formatted text into
//Since we have variable arguments, we need to forward them. We have to use vsprintf instead of sprintf for that.
va_list args;
va_start (args, format);
vsprintf(buffer,format,args);
tft_print_line(x,y,color,bgcolor,font,buffer);
va_end(args);
va_start (args, format); //start the varg-list
vsprintf(buffer,format,args); //let vsprintf render the formatted string
tft_print_line(x,y,color,bgcolor,font,buffer); //print the string as normal text
va_end(args); //end the varg-list
}
bool tft_draw_bitmap_file_unscaled(uint16_t x, uint16_t y, const char* filename) {
//This method reads a .bmp file from the filesystem and tries to draw it.
//Note: The bmp implementation is not complete, it has some limitations and it makes assumptions. See doxygen comment for this method.
//Source Copied and adapted from: http://stackoverflow.com/a/17040962/2606757
FILE_HANDLE* file = filesystem_file_open(filename); //try to open the file
if(file==NULL) { //file opening failed
return false;
}
unsigned char info[54];
if(filesystem_file_read(file,info,54)!=F_OK) { //try to read the 54 byte header
filesystem_file_close(file);
return false; //reading the header failed
}
// extract image height and width from header
uint32_t width = *(uint32_t*)&info[18]; //width in pixel
uint32_t height = *(uint32_t*)&info[22]; //height in pixel
uint16_t depth = *(uint16_t*)&info[28]; //bit's per pixel (color depth)
depth/=8; //we want the number of bytes per pixel
filesystem_file_seek(file,*(uint32_t*)&info[10]); //seek to the place where img data begins
uint32_t row_padded = (width*depth + 3) & (~3); //row size must be aligned to 4 bytes
unsigned char data [row_padded]; //allocate space for one row (incl. padding)
for(int i = 0; i < height; i++) //for each row
{
filesystem_file_read(file,data,row_padded); //read row into buffer
for(int j = 0; j < width*depth; j += depth) //for each pixel
{
unsigned char a,r,g,b;
if(depth==4) { //a,r,g,b 8bit each
a = data[j];
r = data[j+1];
g = data[j+2];
b = data[j+3];
} else if (depth==3) { // b,g,r, 8bit each
a = 255;
r = data[j+2];
g = data[j+1];
b = data[j];
}
if(a!=0) {
//bmp's are stored "bottom-up", so we start drawing at the bottom
tft_draw_pixel(x+j/depth,y+height-1-i,RGB(r,g,b));
}
}
}
filesystem_file_close(file);
return true;
}

View File

@@ -1,6 +1,9 @@
#ifndef TFT_H
#define TFT_H
#include<stdbool.h>
#include<stdint.h>
/**
* @defgroup tft TFT
* The TFT Modul provides access to the display
@@ -13,9 +16,6 @@
/*@{*/
#include<stdbool.h>
#include<stdint.h>
/**
* Creates a 16bit color from 8bit * 3 colors (r,g,b)
* @return
@@ -67,7 +67,7 @@ void tft_draw_line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t
* Draws a pixel onto the display.
* @param x The x-Coordinate of the pixel
* @param y The y-Coordinate of the pixel
* @param The 16-bit color to draw the pixel with
* @param color The 16-bit color to draw the pixel with
*/
void tft_draw_pixel(uint16_t x,uint16_t y,uint16_t color);
@@ -78,7 +78,7 @@ void tft_draw_pixel(uint16_t x,uint16_t y,uint16_t color);
* @param y1 The y-Coordinate of the start-point
* @param x2 The x-Coordinate of the end-point
* @param y2 The y-Coordinate of the end-point
* @param The 16-bit color to draw the pixel with
* @param color The 16-bit color to draw the pixel with
*/
void tft_draw_rectangle(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2, uint16_t color);
@@ -88,7 +88,7 @@ void tft_draw_rectangle(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2, uint16_
* @param y1 The y-Coordinate of the start-point
* @param x2 The x-Coordinate of the end-point
* @param y2 The y-Coordinate of the end-point
* @param The 16-bit color to draw the pixel with
* @param color The 16-bit color to draw the pixel with
*/
void tft_fill_rectangle(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2, uint16_t color);
@@ -103,6 +103,18 @@ void tft_fill_rectangle(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2, uint16_
*/
void tft_draw_bitmap_unscaled(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint16_t* dat);
/**
* Draws a bitmap from the filesystem onto the display without scaling/cropping
* The bitmap must be saved in the windows bitmap format (.bmp) without compression and with 24 (b,g,r) or 32 (a,r,g,b) bits per pixel
* @param x The x-coordinate of the top-left corner to draw the bitmap at
* @param y The y-coordinate of the top-left corner to draw the bitmap at
* @param filename The absolute path to the .bmp file
* @return true on success
*/
bool tft_draw_bitmap_file_unscaled(uint16_t x, uint16_t y, const char* filename);
/**
* Draws the outline of a circle onto the display
* @param x The x-Coordinate of the center point
@@ -151,6 +163,7 @@ void tft_print_line(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, ui
* @param bgcolor The 16-bit background color of the text. You may pass TRANSPARENT as Color
* @param font The Fontnum to use for drawing
* @param format The format string (like printf)
* @param ... The arguments to format (like printf)
*/
void tft_print_formatted(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, uint8_t font, const char* format, ...);

View File

@@ -2,11 +2,23 @@
#include "ll_touch.h"
#include <stdio.h>
#define NUM_AREAS 50 //Number of Structs Reserved in Memory for TouchAreas (e.g Buttons)
TOUCH_AREA_STRUCT* areas[NUM_AREAS] = {NULL};
/* The idea is as follows:
* The user can add "touch-areas" which basically represent a rectangles on the screen.
* Once the user touches such a rectangle with the pen, we forward events to his provided callback.
* Touch events are provided to us from the low level implementation via touch_add_raw_event().
* We then need to check which touch areas are effected by that event
*/
volatile POINT_STRUCT pos;
volatile TOUCH_STATE oldState=TOUCH_UP;
/* Possible improvements:
* Exchange pointer-list "areas" with a linked list. This would ensure that we can always accept new regions
* Implement calibration stuff, and calculate the real coordinates out of the data provided in touch_add_raw_event()
*/
#define NUM_AREAS 50 //Number of Touch Areas we can manage
TOUCH_AREA_STRUCT* areas[NUM_AREAS] = {NULL}; //list with pointers to all managed touch area's
volatile POINT_STRUCT pos; //the last touch point
volatile TOUCH_STATE oldState=TOUCH_UP; //the last touch state
bool touch_init() {
return ll_touch_init();
@@ -14,117 +26,124 @@ bool touch_init() {
bool touch_add_raw_event(uint16_t touchX, uint16_t touchY, TOUCH_STATE state) {
bool penDown = (state==TOUCH_DOWN);
bool oldPenDown = (oldState==TOUCH_DOWN);
oldState=state;
//Update current and old position/state
bool penDown = (state==TOUCH_DOWN);
bool oldPenDown = (oldState==TOUCH_DOWN);
oldState=state;
pos.x=touchX;
pos.y=touchY;
if(penDown)
{
// tftDrawPixel(touchX,touchY,WHITE);
if(!oldPenDown) //First Touch
{
for(int z=0; z < NUM_AREAS; z++) // For every touch area
{
if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 )
{
areas[z]->flags=1; //PenInside=1
if(areas[z]->hookedActions & PEN_DOWN)
areas[z]->callback(areas[z],PEN_DOWN);
}
}
}
else //Second, Third
{
for(int z=0; z < NUM_AREAS; z++) // For every touch area
{
if(areas[z]!=NULL )
{
if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2)
{
if(areas[z]->flags==0) //PenInside ==0
{
areas[z]->flags=1; //PenInside=1
if(areas[z]->hookedActions & PEN_ENTER)
areas[z]->callback(areas[z],PEN_ENTER);
}
}
else if(areas[z]->flags) //PenInside==1
{
areas[z]->flags=0; //PenInside=0
if(areas[z]->hookedActions & PEN_LEAVE)
areas[z]->callback(areas[z],PEN_LEAVE);
}
}
}
}
for(int z=0; z < NUM_AREAS; z++) // For every touch area
{
if(areas[z]!=NULL && areas[z]->hookedActions&PEN_MOVE)
{
if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2)
{
areas[z]->callback(areas[z],PEN_MOVE);
}
}
}
}
else
{
if(oldPenDown) //Was the pen ever down (or was it a too short touch)
{
for(int z=0; z < NUM_AREAS; z++) // For every touch area
{
if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 )
{
areas[z]->flags=0; //PenInside = 0;
if(areas[z]->hookedActions & PEN_UP)
areas[z]->callback(areas[z],PEN_UP);
}
}
}
touchX=0xFFFF;
touchY=0xFFFF;
}
if(penDown) //pen is down now
{
//tft_draw_pixel(touchX,touchY,WHITE);
if(!oldPenDown) //pen wasn't down before (positive edge) => First Touch
{
for(int z=0; z < NUM_AREAS; z++) // For every touch area
{
//Check if pos is inside area
if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 )
{
areas[z]->flags=1; //Save PenInside=1
if(areas[z]->hookedActions & PEN_DOWN) //The user wants to receive pen down events
areas[z]->callback(areas[z],PEN_DOWN); //Send event to user callback
}
}
}
else //Pen was down before => Second, Third event in row
{
for(int z=0; z < NUM_AREAS; z++) // For every touch area
{
if(areas[z]!=NULL )
{
//Check if pos is inside area
if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2)
{
if(areas[z]->flags==0) //Pen was not inside before (PenInside==0)
{
areas[z]->flags=1; //Pen is inside now (PenInside=1)
if(areas[z]->hookedActions & PEN_ENTER) //The user wants to receive pen enter events
areas[z]->callback(areas[z],PEN_ENTER);
}
}
else if(areas[z]->flags) //Pos not inside area, but it was before (PenInside==1)
{
areas[z]->flags=0; //Pen is no longer inside (PenInside=0)
if(areas[z]->hookedActions & PEN_LEAVE) //The user wants to receive pen leave events
areas[z]->callback(areas[z],PEN_LEAVE);
}
}
}
}
for(int z=0; z < NUM_AREAS; z++) // For every touch area
{
if(areas[z]!=NULL && (areas[z]->hookedActions&PEN_MOVE)) //User want's to receive pen move events
{
//Check if pos is inside area
if(touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2)
{
areas[z]->callback(areas[z],PEN_MOVE);
}
}
}
}
else //pen is not down now
{
if(oldPenDown) //but it was down before (negative edge)
{
for(int z=0; z < NUM_AREAS; z++) // For every touch area
{
//Check if pos is inside area
if(areas[z]!=NULL && touchX >= areas[z]->x1 && touchX <= areas[z]->x2 && touchY >= areas[z]->y1 && touchY <= areas[z]->y2 )
{
areas[z]->flags=0; //The pen is no longer inside (PenInside = 0);
if(areas[z]->hookedActions & PEN_UP) //user want's to receive pen up events
areas[z]->callback(areas[z],PEN_UP);
}
}
}
}
return true;
}
bool touch_have_empty(unsigned char num)
{
for(unsigned char i=0; i<NUM_AREAS; i++)
{
if(areas[i]==NULL) num--;
if(num==0) return true;
}
return false;
}
bool touch_register_area(TOUCH_AREA_STRUCT* area) //Registers an Area (fill Struct first). Return false if no more Space in the Pointertable (-->Change NUM_AREAS).
{
for(unsigned char i=0; i<NUM_AREAS; i++)
{
if(areas[i]==NULL)
{
area->flags=0;
areas[i]=area;
return true;
}
}
return false;
//go through pointer array and check for free spaces
for(unsigned char i=0; i<NUM_AREAS; i++)
{
if(areas[i]==NULL) num--; //a free space was found, we need one less
if(num==0) return true; //enough free spaces found
}
return false; //not enough free spaces found
}
void touch_unregister_area(TOUCH_AREA_STRUCT* area)//Unregisters an Area
bool touch_register_area(TOUCH_AREA_STRUCT* area)
{
for(unsigned char i=0; i<NUM_AREAS; i++)
{
if(areas[i]==area)
{
areas[i]=NULL;
break;
}
}
//go through pointer array and check for free space
for(unsigned char i=0; i<NUM_AREAS; i++)
{
if(areas[i]==NULL) //free space found
{
area->flags=0; //we start with empty flags (PenInside=0)
areas[i]=area; //save pointer into list
return true;
}
}
return false; //no free space found
}
void touch_unregister_area(TOUCH_AREA_STRUCT* area)
{
if(area==NULL) return;
//go through pointer array and find the area to remove
for(unsigned char i=0; i<NUM_AREAS; i++)
{
if(areas[i]==area) //area found in pointer array at pos i
{
areas[i]=NULL; //set pointer in list to NULL again
break;
}
}
}

View File

@@ -1,6 +1,9 @@
#ifndef TOUCH_H
#define TOUCH_H
#include<stdbool.h>
#include<stdint.h>
/**
* @defgroup touch Touch
* The Touch module provides access to the touch controller, and executes a callback if a certain region is touched
@@ -12,10 +15,6 @@
*/
/*@{*/
#include<stdbool.h>
#include<stdint.h>
/**
Enum to describe the current Touch State. \sa touch_add_raw_event
*/
@@ -39,6 +38,7 @@ typedef enum {
/**
* Prototype for Event Listeners (called for every occurring, hooked action)
* \note You should NOT execute long running things in this callback nor should you update the gui. But you can call gui_screen_navigate() for instance.
* @param touchArea The pointer to the TOUCH_AREA_STRUCT in which the event occurred
* @param triggeredAction The Action which occurred
*/
@@ -53,8 +53,8 @@ typedef struct {
uint16_t y1; //!< Top Left Y-Coordinate of Area
uint16_t x2; //!< Bottom Right X-Coordinate of Area
uint16_t y2; //!< Bottom Right Y-Coordinate of Area
TOUCH_CALLBACK callback; //!< Callback
uint8_t flags; //!< For internal Used, don't change, don't initialize
TOUCH_CALLBACK callback; //!< Callback which is executed when an event occurred in this Area.
uint8_t flags; //!< For internal use, don't change, don't initialize
} TOUCH_AREA_STRUCT;

Binary file not shown.

Binary file not shown.

BIN
doc/inheritance.dia Normal file

Binary file not shown.

BIN
doc/inheritance.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
emulator/emulated/shot1.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1 +0,0 @@
hallo welt

View File

@@ -44,13 +44,15 @@ void ll_tft_draw_circle(uint16_t x, uint16_t y, uint16_t r, uint16_t color) {
}
uint8_t ll_tft_num_fonts() {
return 1;
return 2;
}
QFont get_font(uint8_t fontnum) {
switch(fontnum) {
case 0:
return QFont("Monospace",8);
case 1:
return QFont("DejaVu Sans Mono",14);
default:
return QFont();
}

View File

@@ -34,6 +34,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), image(DISPLAY_WID
ui->setupUi(this);
image.fill(Qt::black);
currentScale = 1;
ui->widgetDisplay->setMouseTracking(true);
ui->widgetDisplay->installEventFilter(this);
}
void MainWindow::draw_line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
@@ -159,7 +161,28 @@ void MainWindow::mouseMoveEvent(QMouseEvent *evt)
{
//qDebug() << "move" << evt->pos();
checkAndSendEvent(evt->pos(),true);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *evt)
{
if(obj==ui->widgetDisplay) {
switch(evt->type()) {
case QEvent::MouseMove:
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(evt);
QPoint p = (mouseEvent->pos()-QPoint(1,1))/currentScale;
if(p.x()<DISPLAY_WIDTH && p.y()<DISPLAY_HEIGHT) {
ui->txtMousePos->setText(QString("Mouse Position: (%1,%2)").arg(p.x()).arg(p.y()));
}
}
break;
default: break;
}
}
return false;
}

View File

@@ -25,6 +25,7 @@ public:
void draw_char(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, QFont font, char c);
protected:
bool eventFilter(QObject * obj , QEvent * env);
void paintEvent(QPaintEvent * evt);
void mousePressEvent(QMouseEvent* evt);
void mouseReleaseEvent(QMouseEvent* evt);

View File

@@ -43,6 +43,13 @@
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="txtMousePos">
<property name="text">
<string>Mouse Position: out of bounds</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">