永远的GitHub地址: https://github.com/JianBiHua/go_360_safe
如图效果(显示跟隐藏,都是慢慢显示的带尖角的窗体):
这里有几个小问题我还没有解决
- 动画时长设置太短(比如0.3秒),左右会多出一部分,
- 显示时,会闪现显示一个子控件,然后才正常动画
- ticker有时会崩溃.
显示: 当鼠标滑动到"简笔画/登陆/360图标"这块时
隐藏: 当鼠标厉害显示区域以及登陆框区域时。
核心代码如下:
A. 绘制代码:
// 画图
// 绘制较简单,就不将painter作为全局变量了
func (p *PopupWidget) onPaint (event *gui.QPaintEvent) {
//// 创建画笔
var device = p.BackingStore().PaintDevice() //window.Painters[0]
var painter = gui.NewQPainter2(device)
// 反走样
painter.SetRenderHint(gui.QPainter__Antialiasing, true)
var shadowPath = gui.NewQPainterPath()
var path = gui.NewQPainterPath()
// 我这里只画一个向上的,其它基本一样,就是坐标不一样罢了
// 我最讨厌go里面的不同类型的运算,转来转去,麻烦....
switch p.arrowDirection {
case ArrowDirectionUp:
// 办法1:这个是通过遮罩实现的,但是看不到左右的边,如果有阴影,效果就不是很好了。
//// 这些代码是显示一个完整的带尖角的代码。
//path.MoveTo2(0, ArrowWidth)
//path.LineTo2(p.luDis-ArrowWidth/2.0, ArrowWidth)
//// 下面两句是画箭头的
//path.LineTo2(p.luDis, 0)
//path.LineTo2(p.luDis+ArrowWidth/2.0, ArrowWidth)
//path.LineTo2(float64(p.Width()), ArrowWidth)
//path.LineTo2(float64(p.Width()), float64(p.Height()))
//path.LineTo2(0, float64(p.Height()))
//path.LineTo2(0, ArrowWidth)
//
//// 添加一个遮罩层,我们的遮罩应该以箭头为中心
//// 这样就能算出实际应该遮罩的位置,改变currentProgress, 就出现了从箭头位置向两边延伸的效果
//path2.AddRect2(p.luDis*(1-p.currentProgress), 0, p.currentProgress*float64(p.Width()), float64(p.Height()))
//painter.DrawPath(path2)
// 办法2,直接根据currentProgress,动态画这个带尖角的图形,可以稍微往里留一两个像素,那么就能看到阴影了.
// 注意,这个图形最小宽度应为 ArrowWidth+(ShadowWidth*2), 左右留出ShadowWidth大小的像素画阴影。
// 这里要考虑两种情况,
// 第一: currentProgress*p.width() < ArrowWidth+(ShadowWidth*2) 时
// 第二: currentProgress*p.width() >= ArrowWidth+(ShadowWidth*2) 时
// 设置颜色。
//=======================================================================
// 阴影的原理,就是将图形, 向左或者向右,向上或者向下移动一定像素,图形透明度降低
// 画阴影
painter.SetPen3(core.Qt__NoPen)
painter.SetBrush(gui.NewQBrush10(p.getShadowColor()))
var rw = p.currentProgress*float64(p.Width()-ShadowWidth*2)
var rx = 0.0
if rw < ArrowWidth+ShadowWidth*2 {
rx = p.luDis-ArrowWidth/2+2
} else {
rx = p.luDis*(1-p.currentProgress)+2
}
// 这样就画出了不同的图形,最小宽度ArrowWidth+(ShadowWidth*2)
shadowPath.MoveTo2(rx+p.pX, ArrowWidth+2+p.pY)
shadowPath.LineTo2(p.luDis-ArrowWidth/2+p.pX, ArrowWidth+2+p.pY)
// 画角
shadowPath.LineTo2(p.luDis+p.pX, 0+2+p.pY)
shadowPath.LineTo2(p.luDis+ArrowWidth/2+p.pX, ArrowWidth+2+p.pY)
shadowPath.LineTo2(rx+rw+p.pX, ArrowWidth+2+p.pY)
// 留出底部的阴影区域
shadowPath.LineTo2(rx+rw+p.pX, float64(p.Height())-ShadowWidth+2+p.pY)
shadowPath.LineTo2(rx+p.pX, float64(p.Height())-ShadowWidth+2+p.pY)
shadowPath.LineTo2(rx+p.pX, ArrowWidth+2+p.pY)
// 显示图形。
painter.DrawPath(shadowPath)
//=======================================================================
// 画正常的图形
// 设置颜色。
painter.SetPen2(gui.NewQColor3(0x7F, 0x7F, 0x7F, 0x7F))
painter.SetBrush(gui.NewQBrush3(gui.NewQColor3(0xF5, 0xF5, 0xF5, 0xFF),1))
rw = p.currentProgress*float64(p.Width()-ShadowWidth*2)
rx = 0.0
if rw < ArrowWidth+ShadowWidth*2 {
rx = p.luDis-ArrowWidth/2
} else {
rx = p.luDis*(1-p.currentProgress)
}
// 这样就画出了不同的图形,最小宽度ArrowWidth+(ShadowWidth*2)
path.MoveTo2(rx+p.pX, ArrowWidth+p.pY)
path.LineTo2(p.luDis-ArrowWidth/2+p.pX, ArrowWidth+p.pY)
// 画角
path.LineTo2(p.luDis+p.pX, 0+p.pY)
path.LineTo2(p.luDis+ArrowWidth/2+p.pX, ArrowWidth+p.pY)
path.LineTo2(rx+rw+p.pX, ArrowWidth+p.pY)
// 留出底部的阴影区域
path.LineTo2(rx+rw+p.pX, float64(p.Height())-ShadowWidth+p.pY)
path.LineTo2(rx+p.pX, float64(p.Height())-ShadowWidth+p.pY)
path.LineTo2(rx+p.pX, ArrowWidth+p.pY)
// 显示图形。
painter.DrawPath(path)
case ArrowDirectionDown:
}
// 结束绘制
painter.End()
painter.DestroyQPainter()
}
B. 动画效果.
// 显示
// interval:显示的持续时间, 1000 = 1s
// f: 动画结束时相应,可能某些人喜欢在动画结束后执行一些操作,就可以这么写
func (p *PopupWidget) Show2 (interval int, f func(widget *PopupWidget)) {
if !p.isShow {
// 只有隐藏状态才显示
p.ticker = time.NewTicker(20 * time.Millisecond)
// 默认遮罩,什么也不显示
p.SetMask2(gui.NewQRegion2(int(p.luDis),
0,
0,
p.Height(),
gui.QRegion__Rectangle))
p.loginWidget.ShowDefault()
go func() {
defer p.ticker.Stop()
var ticker = 0
for {
select {
case <-p.ticker.C:
// 1.0 / (interval/20)
p.currentProgress += 20.0 / float64(interval)
if p.currentProgress > 1 {
p.currentProgress = 1
// 都跑到了,就直接让它结束吧,
ticker = interval
}
// 修改遮罩位置
// 添加一个遮罩层,我们的遮罩应该以箭头为中心
// 这样就能算出实际应该遮罩的位置,改变currentProgress, 就出现了从箭头位置向两边延伸的效果
p.SetMask2(gui.NewQRegion2(int(p.luDis*(1-p.currentProgress)),
0,
int(p.currentProgress*float64(p.Width())),
p.Height(),
gui.QRegion__Rectangle))
// 计数
ticker += 20
if ticker >= interval {
//
p.isShow = true
// 告诉上层,动画完成了.
if f != nil {
f (p)
}
return
}
// 刷新
p.Update()
}
}
} ()
}
}
C. 控件显示代码
mw.window.ConnectEvent(func(event *core.QEvent) bool {
if event.Type() == core.QEvent__HoverMove {
// 获取当前鼠标在屏幕上的绝对坐标
var pos = core.NewQPoint2(gui.QCursor_Pos().X(), gui.QCursor_Pos().Y())
// 将全局左边转换成window上的坐标
var pos2 = mw.window.MapFromGlobal(pos)
var x = pos2.X()
var y = pos2.Y()
// 如果是鼠标滑动
// 处理鼠标的滑动
// 实现的逻辑是,
// 当鼠标移动到loginPopupWidget上或者移动到登陆区域,才显示登陆框,否则,如果显示了则隐藏。
if mw.loginPopupWidget == nil {
// 如果loginPopupWidget不存在,移动到登陆区域,才显示登陆框
if mw.loginWidget.Widget().Geometry().Contains3(x, y) {
mw.loginPopupWidget = NewPopupWidget (mw.window, ArrowDirectionUp, 90, 700, 90)
mw.loginPopupWidget.SetGeometry3(700, 90, 180, 100)
mw.loginPopupWidget.Show(1*1000)
mw.loginPopupWidget.SetWindowFlags(core.Qt__WindowStaysOnTopHint);
mw.loginPopupWidget.ShowNormal()
}
} else {
// 如果显示了。鼠标移动到除登录区以及loginPopupWidget区域外时,隐藏loginPopupWidget
if !mw.loginWidget.Widget().Geometry().Contains3(x, y) &&
!mw.loginPopupWidget.Geometry().Contains3(x, y) {
mw.loginPopupWidget.Hide2(1*1000, func(widget *PopupWidget) {
if mw.loginPopupWidget != nil {
mw.loginPopupWidget.HideDefault()
//当隐藏后销毁
mw.loginPopupWidget.DestroyQWidget()
mw.loginPopupWidget = nil
}
})
}
}
}
// 如果没有这句,你会发现波浪不动了, 因为你把所有的事件都处理了,下面收不到消息了
return mw.window.EventDefault(event)
})