只用到了类的基本语法,初次接触就去看“菜鸟教程”的C++的类的部分
基于ARMGCC
使用FreeRTOS,内存管理为heap4或heap5,可防止出现内存碎片
使用1bit像素模式,其它的同理
extern "C"
{
extern void *pvPortMalloc(size_t xWantedSize);
extern void vPortFree(void *pv);
}
//重载new和delete
void *operator new(size_t size)
{
return pvPortMalloc(size);
}
void operator delete(void *pointer)
{
vPortFree(pointer);
}
void (*fill_srcee_)(uint16_t x, uint16_t y, uint16_t width, uint16_t height, void *data);
//坐标系是左上角为原点,x向右递增,y向下递增!
const uint16_t main_window_width_ = 128;
const uint16_t main_window_height_ = 64; //所有的坐标系将以这个为准 //坐标以左上角为原点(0,0),x向右递增,y向下递增
const uint8_t origin_ = 0; //仅用于代替坐标原点的0
window *window::main_window_ = nullptr, *window::desktop_window_ = nullptr;
uint8_t window::creat_main_window_flag_ = false;
class window
{
public: //为了简化流程,这里用public属性
int16_t x_, y_; //窗口的起点(相对于父窗口的)
uint16_t width_, height_; //窗口宽高,单位是像素。>=1
static window *main_window_, *desktop_window_; //主窗口地址,桌面窗口地址
window *previous_ = nullptr, *next_ = nullptr; //同级窗口之间的联系。也是绘制顺序,previou比next先绘制。选中的窗口会被移动到树的最后面,则该窗口必定覆盖在其它窗口上
window *first_child_ = nullptr, *last_child_ = nullptr; //第一个和最后一个child地址
window *parent_ = nullptr; //父窗口地址
static uint8_t creat_main_window_flag_; //主窗口是否建立的标志
void *display_buffer_; //画面缓存。要注意这是void类型的数组,对其元素取值/赋值时要转换类型,并且类型必须要对。为了兼容各种位数的像素,1bit,16bit等
window(int16_t x = 0, int16_t y = 0, uint16_t width = main_window_width_, uint16_t height = main_window_height_);
~window();
void draw_pixel(uint16_t x, uint16_t y, uint32_t color); //向自己的画面混村里画个像素点
uint32_t get_pixel(uint16_t x, uint16_t y); //得到指定位置的像素点颜色
static void draw_main_window_pixel(uint16_t x, uint16_t y, uint32_t color) //向主窗口(帧缓存)里画像素点
void connect(window *parent); //连接一个窗口,该窗口作为自己的父窗口
void disconnect(void); //与父窗口断开连接
int16_t get_x2(void) { return x_ + width_ - 1; }
int16_t get_y2(void) { return y_ + height_ - 1; }
static void set_fill_srceen_pt(void (*fill_srcee)(uint16_t x, uint16_t y, uint16_t width, uint16_t height, void *data)); //设置屏幕的绘图函数
static void updata_all_window(void); //将所有连接到树状图上的窗口的画面更新到主窗口(帧缓存)里
void updata_my_window(uint16_t display_x, uint16_t display_y, uint16_t display_width, uint16_t display_height, uint16_t x, uint16_t y); //将自己的窗口的画面更新到主窗口(帧缓存)里。由updata_all_window调用
uint8_t calculate_overlap_range(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t &p_sta_x, uint16_t &p_sta_y, uint16_t &my_sta_x, uint16_t &my_sta_y, uint16_t &my_width, uint16_t &my_height); //计算两个矩形的相交部分的面积和相交部分的起点
};
window::window(int16_t x, int16_t y, uint16_t width, uint16_t height) : x_(x), y_(y), width_(width > 0 ? width : 1), height_(height > 0 ? height : 1)
{
if (!creat_main_window_flag_)
{
creat_main_window_flag_ = true;
desktop_window_ = this;
//建立主窗口
main_window_ = new window;
connect(main_window_);
x_ = y_ = origin_;
width_ = main_window_width_;
height_ = main_window_height_;
}
display_buffer_ = new uint8_t[width_ * (height_ / 8 + (height_ % 8 ? 1 : 0))]; //有余数再加一行
};
window::~window()
{
delete[] (uint8_t *)display_buffer_;
if (this == desktop_window_)
delete main_window_;
}
void window::draw_pixel(uint16_t x, uint16_t y, uint32_t color)
{
if (x >= width_ || y >= height_)
return;
uint16_t addr = y / 8 * width_ + x;
uint8_t src = ((uint8_t *)display_buffer_)[addr];
color ? src = (0x01 << (y % 8)) | src : src = (~(0x01 << (y % 8))) & src;
((uint8_t *)display_buffer_)[addr] = src;
}
uint32_t window::get_pixel(uint16_t x, uint16_t y)
{
return (0x01 << (y % 8) & ((uint8_t *)display_buffer_)[y / 8 * width_ + x]) ? 1 : 0;
}
void window::draw_main_window_pixel(uint16_t x, uint16_t y, uint32_t color)
{
if (x >= main_window_width_ || y >= main_window_height_)
return;
uint16_t addr = y / 8 * main_window_width_ + x;
uint8_t src = ((uint8_t *)(main_window_->display_buffer_))[addr];
color ? src = (0x01 << (y % 8)) | src : src = (~(0x01 << (y % 8))) & src;
((uint8_t *)(main_window_->display_buffer_))[addr] = src;
}
void window::set_fill_srceen_pt(void (*fill_srcee)(uint16_t x, uint16_t y, uint16_t width, uint16_t height, void *data))
{
fill_srcee_ = fill_srcee;
}
void display(uint16_t x, uint16_t y, uint16_t width, uint16_t height, void *data);
这是display函数的原型,其功能如下
传入绘制区域的起始坐标x,y,绘制区域的像素堆的宽高(大于等于1),绘制数据的第一个字节的地址
绘制的像素数量是width * height,data要被转换为(uint8_t*)类型,并且一个字节包含8个竖着的像素点
对于12864的单色屏
display(0, 0, 128, 64, main_window_->display_buffer_);
{
//简单点,循环128 * 8次,将(uint8_t *)data发出去
}
int main()
{
//省略系统的初始化
window::set_fill_srceen_pt(display); //添加绘图函数的指针。不写这个也没关系,显示函数做了判空处理,不会去操作空指针,但是画面就得去内存里面看了
window a;
for(;;)
{
window::updata_all_window();
}
}
第一个窗口的创建和绘制就完成了
来梳理整个过程
1、
window a;
创建了一个window类的对象。
执行过程如下:
//使用设定的初值初始化x_、y_、width_、height_的数据
声明:window(int16_t x = 0, int16_t y = 0, uint16_t width = main_window_width_, uint16_t height = main_window_height_);
window::window(int16_t x, int16_t y, uint16_t width, uint16_t height) : x_(x), y_(y), width_(width > 0 ? width : 1), height_(height > 0 ? height : 1)
{
if (!creat_main_window_flag_) //creat_main_window_flag_是false,!false则等于true,进入
{
creat_main_window_flag_ = true; //防止第二次进入
desktop_window_ = this; //将自己的地址给desktop_window_
//建立主窗口
main_window_ = new window; //再创建一个对象,但因为是第二次进入构造函数,所以无法进入这个if语句,将直接执行申请内存的部分,并退出构造函数,然后将地址给main_window_
connect(main_window_); //连接到main_window_上
//强制改为设定的初值
x_ = y_ = origin_;
width_ = main_window_width_;
height_ = main_window_height_;
}
//申请内存
display_buffer_ = new uint8_t[width_ * (height_ / 8 + (height_ % 8 ? 1 : 0))]; //有余数再加一行
};
connect和disconnect函数是整个系统的基石!
connect和disconnect函数是整个系统的基石!
connect和disconnect函数是整个系统的基石!
动手动笔在纸上画!
动手动笔在纸上画!
动手动笔在纸上画!
懂了这个就会了一半!
懂了这个就会了一半!
懂了这个就会了一半!
连接到从desktop开始的树状图后,遍历相关的函数才会对窗口进行处理
断开与树的连接后,将不再被遍历函数操作
例如虚拟键盘连接到树上后,就会被显示出来,断开连接后就不显示了
窗口的最小化与最大化也是这样,但不代表缓存的资源消失了
mainwin //最终的画面在这里,作为帧缓存。以下称为主窗口
|
desktop //第一个窗口。大小和帧缓存是一样的。从这个窗口开始向下遍历
| |
w1 w2 //其它窗口
void window::connect(window *parent) //连接父窗口。跟在父窗口的最后一个子窗口右边
{
//安全第一
//如果自己是main_window_则退出、如果自己是desktop_window_而要连接的父窗口不是main_window_则退出、连接自己就退出、连接空指针就退出、如果有父窗口则退出
if (this == main_window_ || (this == desktop_window_ && parent != main_window_) || parent == this || parent == 0 || parent_)
return;
parent_ = parent; //parent作为自己的父窗口
previous_ = parent->last_child_; //将父窗口的最后一个child作为自己的上一个窗口
if (parent_->last_child_) //如果父窗口有最后一个child
parent_->last_child_->next_ = this; //把自己作为该child的下一个同级窗口
parent_->last_child_ = this; //把自己作为父窗口的last_child_
if (parent_->first_child_ == nullptr) //如果父窗口的第一个child为空
parent_->first_child_ = this; //则把自己作为父窗口的第一个child
}
void window::disconnect(void)
{
if (this == main_window_ || this == desktop_window_ || parent_ == nullptr)
return;
//处理其它窗口
if (previous_)
previous_->next_ = next_;
if (next_)
next_->previous_ = previous_;
if (parent_->first_child_ == this)
parent_->first_child_ = next_;
if (parent_->last_child_ == this)
parent_->last_child_ = previous_;
//处理自己的窗口
previous_ = nullptr, next_ = nullptr;
parent_ = nullptr;
}
明白了以上两个函数再看window::updata_all_window();,知道各个窗口的绘制顺序,怎么计算绘制的区域。
void window::updata_all_window()
{
if (!desktop_window_)
return;
desktop_window_->updata_my_window(origin_, origin_, main_window_width_, main_window_height_, origin_, origin_);
//向屏幕绘图
if(fill_srcee_)
fill_srcee_(origin_, origin_, main_window_width_, main_window_height_, main_window_->display_buffer_);
}
//传入父窗口可显示区域的相对坐标、宽高,自己窗口的绝对位置
void window::updata_my_window(uint16_t display_x, uint16_t display_y, uint16_t display_width, uint16_t display_height, uint16_t x, uint16_t y)
{
uint16_t p_x, p_y, my_x, my_y, d_width, d_height; //自己窗口的可显示区域相对于父窗口的坐标,在自己窗口里的起始坐标,可显示区域的长宽
if (calculate_overlap_range(display_x, display_y, display_width, display_height, p_x, p_y, my_x, my_y, d_width, d_height))
{
for (uint16_t h = 0; h < d_height; ++h)
{
for (uint16_t w = 0; w < d_width; ++w)
draw_main_window_pixel(x - x_ + p_x + w, y - y_ + p_y + h, get_pixel(w + my_x, h + my_y));
}
if (first_child_)
first_child_->updata_my_window(my_x, my_y, d_width, d_height, first_child_->get_x() + x, first_child_->get_y() + y);
}
if (next_)
next_->updata_my_window(display_x, display_y, display_width, display_height, x - x_ + next_->get_x(), y - y_ + next_->get_y());
}
//计算自己和另一个矩形的相交部分的起点和大小
uint8_t window::calculate_overlap_range(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t &p_sta_x, uint16_t &p_sta_y, uint16_t &my_sta_x, uint16_t &my_sta_y, uint16_t &my_width, uint16_t &my_height)
{
if (x_ > x + width - 1 || get_x2() < x || y_ > y + height - 1 || get_y2() < y) //自己不在可显示的范围内
return false;
uint16_t x_r_min = (x + width - 1) > get_x2() ? get_x2() : (x + width - 1);
uint16_t x_l_max = x > x_ ? x : x_;
uint16_t y_t_max = y > y_ ? y : y_;
uint16_t y_b_min = (y + height - 1) > get_y2() ? get_y2() : (y + height - 1);
my_width = x_r_min - x_l_max + 1;
my_height = y_b_min - y_t_max + 1;
p_sta_x = x_l_max;
p_sta_y = y_t_max;
my_sta_x = x_l_max - x_;
my_sta_y = y_t_max - y_;
return true;
}
这个是判断焦点落在那个窗口上
首先用last_child_,去到整个系统里最后一个窗口,也是最顶端的一个窗口,也是唯一一个不会被覆盖的窗口
焦点的绝对坐标会被转换成窗口的相对坐标,让焦点和“判断焦点是否在自己窗口里”的窗口处于同一坐标系
从最顶端的一个窗口依次向底端窗口发展。一旦确定焦点所在的窗口,将不断的返回,直到退出所有的焦点函数
window *window::get_focus_in_window(int16_t x, int16_t y)
{
static window *hit;
hit = nullptr;
if (x >= x_ && x <= get_x2() && y >= y_ && y <= get_y2()) //只有点在自己窗口内才会去子窗口或者自我判断
{
if (last_child_) //去往最后一个子窗口
{
hit = last_child_->get_focus_in_window(x - x_, y - y_); //换算成相对于父窗口的坐标
if (hit != nullptr) //如果焦点在子窗口上则返回获得焦点的窗口,否则进入自我判断
return hit;
}
if (x >= x_ && x <= get_x2() && y >= y_ && y <= get_y2())
{
hit = this;
return hit;
}
}
if (previous_) //去往上一个同级窗口
{
hit = previous_->get_focus_in_window(x, y); //仍使用相对于父窗口的坐标
if (hit != nullptr)
return hit;
}
return hit;
}