告别Qt Slider!用纯C++打造更轻量的TpSlider组件
组件运行效果展示
组件概述
TpSlider组件简介
TpSlider是PiXSingleGUI库中的可拖动滑块组件,支持水平和垂直两种方向的滑动操作。TpSlider.h:13-17
该组件提供了完整的用户交互功能,包括鼠标拖拽、数值范围设置和实时反馈机制。
核心特性
- 双向支持:支持水平(Horizon)和垂直(Vertical)两种滑动方向
- 精确控制:提供数值范围设置和当前值获取功能
TpSlider.h:24-37
- 事件驱动:基于信号槽机制的松耦合通信
TpSlider.h:40-48
- 高性能渲染:优化的绘制流程,适用于嵌入式环境
核心架构设计
继承关系与基类依赖
TpSlider继承自tpChildWidget基类,这种设计遵循了PiXSingleGUI库的统一架构模式。TpSlider.h:10
基类提供了以下核心能力:
- 事件处理框架:统一的鼠标、键盘事件分发机制
- 渲染管理:标准化的绘制接口和更新调度
- 内存管理:自动的生命周期管理和资源释放
数据结构设计
组件采用PIMPL(PointertoImplementation)设计模式,将所有实现细节封装在TpSliderData结构中:TpSlider.cpp:7-25
struct TpSliderData
{int32_t maxValue = 100;int32_t minValue = 0;double value = 0;TpSlider::SliderDirect direct = TpSlider::Horizon;// 顶点矩形区域bool isPressVertex = false;ItpRect vertexRect;ItpPoint pressPoint;// 一个间隔值对应的像素double valuePx = 0;// 是否正在拖拽调整进度,拖拽过程不响应setValue事件bool isDrag = false;
};
数据结构的关键设计:
- 数值管理:
minValue
、maxValue
、value
三元组管理滑块状态 - 交互状态:
isPressVertex
、isDrag
标志位控制拖拽逻辑 - 性能优化:
valuePx
预计算像素比例,vertexRect
缓存顶点区域
功能实现详解
数值范围管理
滑块的数值管理通过setRange()
和setValue()
方法实现:TpSlider.cpp:48-68
void TpSlider::setRange(const int32_t &min, const int32_t &max)
{TpSliderData *sliderData = static_cast<TpSliderData *>(data_);sliderData->minValue = min;sliderData->maxValue = max;if (sliderData->maxValue < sliderData->minValue)sliderData->maxValue = sliderData->minValue + 1;if (sliderData->value < sliderData->minValue)sliderData->value = sliderData->minValue;else if (sliderData->value > sliderData->maxValue)sliderData->value = sliderData->maxValue;else{}rangeChanged.emit(sliderData->minValue, sliderData->maxValue);update();
}
边界处理逻辑:
- 自动修正无效范围(最大值小于最小值)
- 当前值超出范围时自动调整到边界值
- 范围变化时触发
rangeChanged
信号通知
鼠标事件处理机制
按下事件处理
鼠标按下事件的核心是区域检测和状态初始化:TpSlider.cpp:104-129
bool TpSlider::onMousePressEvent(TpMouseEvent *event)
{TpSliderData *sliderData = static_cast<TpSliderData *>(data_);sliderData->isPressVertex = false;sliderData->isDrag = false;if (event->button() != BUTTON_LEFT)return true;ItpPoint mousePoint = event->pos();if (sliderData->vertexRect.contains(mousePoint)){if (sliderData->direct == TpSlider::Horizon)sliderData->valuePx = 1.0 * (sliderData->maxValue - sliderData->minValue) / width();elsesliderData->valuePx = 1.0 * (sliderData->maxValue - sliderData->minValue) / height();sliderData->pressPoint = mousePoint;sliderData->isPressVertex = true;sliderData->isDrag = true;}return true;
}
关键算法:
- 只响应左键点击且点击在顶点区域内
- 根据滑块方向预计算像素-数值转换比例
- 设置拖拽状态标志,防止外部
setValue
干扰
移动事件处理
拖拽过程中的数值计算是组件的核心算法:TpSlider.cpp:131-183
bool TpSlider::onMouseMoveEvent(TpMouseEvent *event)
{TpSliderData *sliderData = static_cast<TpSliderData *>(data_);if (sliderData->isPressVertex){ItpPoint curMotionPoint = event->pos();int32_t offsetPx = 0;if (sliderData->direct == TpSlider::Horizon){offsetPx = curMotionPoint.x - sliderData->pressPoint.x;}else{offsetPx = sliderData->pressPoint.y - curMotionPoint.y;}// std::cout << " offsetPx " << offsetPx << std::endl;if (std::abs(offsetPx) >= sliderData->valuePx){int32_t oldValue = sliderData->value;sliderData->value += (offsetPx * sliderData->valuePx);if (sliderData->value > sliderData->maxValue){sliderData->value = sliderData->maxValue;}else if (sliderData->value < sliderData->minValue){sliderData->value = sliderData->minValue;}else{}// std::cout << " sliderData->value " << sliderData->value << std::endl;int32_t newValue = sliderData->value;if (newValue != oldValue){valueChanged.emit(newValue);}}sliderData->pressPoint = curMotionPoint;update();}return true;
}
算法特点:
- 方向适配:水平方向计算X轴偏移,垂直方向计算Y轴偏移(注意Y轴反向)
- 增量更新:基于像素偏移量计算数值变化
- 防抖处理:只有数值真正改变时才触发
valueChanged
信号
信号槽通信机制
组件提供两个核心信号用于外部通信:TpSlider.h:41-48
public
signals:
/// @brief 值变化信号
/// @param int 当前值
declare_signal(valueChanged, int32_t);/// @brief 范围变化信号
/// @param int 当前最小值
/// @param int 当前最大值
declare_signal(rangeChanged, int32_t, int32_t);
PiXSingleGUI的信号槽系统基于模板实现,提供类型安全的事件通信:TpSignalSlot.h:91-130
渲染系统实现
绘制流程
滑块的绘制分为三个层次:背景轨道、进度填充和拖拽顶点。TpSlider.cpp:192-228
TpSliderData *sliderData = static_cast<TpSliderData *>(data_);// tpChildWidget::onPaintEvent(event);
TpShared<TpCssData> curCssData = currentStatusCss();TpCanvas *painter = event->canvas();// 整体高度、宽度;分成4份。进度条1份,顶点2份,浅色顶点4份
uint32_t bgWidth = width();
uint32_t bgHeight = height();
uint32_t bgX = 0;
uint32_t bgY = 0;// 不能用父类绘制,绘制背景色
ItpRect rect = event->rect();
if (objectType() == TP_FLOAT_OBJECT)
{if ((curCssData->backgroundColor() & 0xff) != 0xff){painter->erase();}
}if (sliderData->direct == TpSlider::Horizon)
{bgHeight = height() / 4.0;bgY = (height() - bgHeight) / 2.0;painter->roundedBox(0, bgY, rect.w, bgY + bgHeight, roundCorners(), curCssData->backgroundColor());
}
else
{bgWidth = width() / 4.0;bgX = (width() - bgWidth) / 2.0;painter->roundedBox(bgX, 0, bgX + bgWidth, rect.h, roundCorners(), curCssData->backgroundColor());
}
方向适配渲染
水平方向渲染:TpSlider.cpp:240-272
if (sliderData->direct == TpSlider::Horizon)
{circleRadius = height() / 4.0 * 2.0 / 2.0;valueWidth = valuePercent * width();if (valueWidth != 0)painter->roundedBox(0, bgY, valueWidth, bgY + bgHeight, roundCorners(), curCssData->subColor());int32_t circleX = valueWidth;if (circleX == 0){circleX = circleRadius;}else if (circleX == width()){circleX = width() - circleRadius;}else{}// 绘制淡色圆形顶点painter->filledCircle(circleX, height() / 2.0, height() / 2.0, lightSubColor);// 绘制圆形顶点painter->filledCircle(circleX, height() / 2.0, circleRadius, subColor);// 记录顶点区域sliderData->vertexRect.x = circleX - circleRadius;sliderData->vertexRect.y = height() / 2.0 - circleRadius;sliderData->vertexRect.w = circleRadius * 2;sliderData->vertexRect.h = circleRadius * 2;
}
- 轨道高度为组件高度的1/4
- 顶点位置根据数值百分比计算
- 边界处理确保顶点不超出范围
垂直方向渲染:TpSlider.cpp:274-307
else
{circleRadius = width() / 4.0 * 2.0 / 2.0;valueWidth = valuePercent * height();if (valueWidth != 0)painter->roundedBox(bgX, height() - valueWidth, bgX + bgWidth, height(), roundCorners(), subColor);int32_t circleY = height() - valueWidth;if (circleY == 0){circleY = circleRadius;}else if (circleY == height()){circleY = height() - circleRadius;}else{}// 绘制淡色圆形顶点painter->filledCircle(width() / 2.0, circleY, width() / 2.0, lightSubColor);// 绘制圆形顶点painter->filledCircle(width() / 2.0, circleY, circleRadius, subColor);// 记录顶点区域sliderData->vertexRect.x = width() / 2.0 - circleRadius;sliderData->vertexRect.y = circleY - circleRadius;sliderData->vertexRect.w = circleRadius * 2;sliderData->vertexRect.h = circleRadius * 2;
}
- 轨道宽度为组件宽度的1/4
- 从底部向上填充(符合用户直觉)
- Y坐标计算考虑垂直方向特殊性
样式系统集成
TpSlider组件集成了PiXSingleGUI的样式系统,支持通过CSS配置外观:TpSlider.cpp:197
// tpChildWidget::onPaintEvent(event);
TpShared<TpCssData> curCssData = currentStatusCss();
可配置的样式属性:
backgroundColor
:轨道背景色subColor
:进度填充色和顶点颜色roundCorners
:圆角半径设置
使用示例程序
#include "TpApp.h"
#include "TpFixScreen.h"
#include "TpLabel.h"
#include "TpSlider.h"
#include "TpFont.h"int32_t main(int32_t argc, char *argv[])
{TpApp app(argc, argv);TpFixScreen *vScreen = new TpFixScreen();vScreen->setBackGroundColor(_RGBA(128, 128, 128, 255));vScreen->setVisible(true); // vScreen setvisible will be update displayapp.bindVScreen(vScreen);TpLabel *valueText = new TpLabel(vScreen);valueText->setText(TpString::number(50));valueText->setAlign(tinyPiX::AlignCenter);valueText->font()->setFontColor(_RGB(255, 255, 255),_RGB(255, 255, 255));valueText->font()->setFontSize(128);valueText->setWidth(600);valueText->setHeight(400);valueText->move(20, 150);TpSlider *slider = new TpSlider(vScreen);slider->setValue(50);slider->setSize(500, 10);slider->move(20, 20);TpSlider *vSlider = new TpSlider(vScreen);vSlider->setDirection(TpSlider::Vertical);vSlider->setValue(50);vSlider->setSize(10, 500);vSlider->move(650, 20);connect(slider, valueChanged, [=](int32_t value){ valueText->setText(TpString::number(value));vSlider->setValue(value); });connect(vSlider, valueChanged, [=](int32_t value){ valueText->setText(TpString::number(value));slider->setValue(value); });vScreen->update();return app.run();
}
TinyPiXOS开发者联盟
源码级支持 + 真实项目:TinyPiXOS开发者联盟招募中,国产自主轻量级嵌入式设备桌面操作系统交流社群。
获取开发资料
官网网站
B 站视频
感谢支持和关注,如果对项目感兴趣,请点赞、收藏和转发!