• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 package ohos.devtools.views.layout.chartview;
17 
18 import com.intellij.ui.components.JBPanel;
19 import ohos.devtools.views.charts.model.ChartDataRange;
20 import ohos.devtools.views.charts.model.ChartStandard;
21 import ohos.devtools.views.common.ColorConstants;
22 
23 import java.awt.Dimension;
24 import java.awt.event.MouseAdapter;
25 import java.awt.event.MouseEvent;
26 import java.math.BigDecimal;
27 
28 import static ohos.devtools.views.common.LayoutConstants.INITIAL_VALUE;
29 import static ohos.devtools.views.layout.chartview.utils.OperationUtils.divide;
30 import static ohos.devtools.views.layout.chartview.utils.OperationUtils.multiply;
31 
32 /**
33  * 自定义滚动条
34  *
35  * @since 2021/11/22
36  */
37 public class ProfilerScrollbar extends JBPanel {
38     /**
39      * 滚动条的高度
40      */
41     private static final int BAR_HEIGHT = 8;
42 
43     /**
44      * 滚动条的最小宽度
45      */
46     private static final int MIN_WIDTH = 20;
47 
48     /**
49      * 滚动条的父面板
50      */
51     private final ProfilerChartsView parent;
52 
53     /**
54      * 滚动条可移动范围的宽度
55      */
56     private int moveScopeWidth;
57 
58     /**
59      * 拖动时的滚动条控件
60      */
61     private JBPanel bar;
62 
63     /**
64      * 拖动滚动条时,滚动条起点坐标和鼠标点之间的偏移量
65      */
66     private int offsetPixel = INITIAL_VALUE;
67 
68     /**
69      * 构造函数
70      *
71      * @param parent 父面板
72      */
ProfilerScrollbar(ProfilerChartsView parent)73     public ProfilerScrollbar(ProfilerChartsView parent) {
74         this.parent = parent;
75         initBar();
76         addListener();
77     }
78 
79     /**
80      * 初始化ScrollBar
81      */
initBar()82     private void initBar() {
83         if (parent == null) {
84             return;
85         }
86         this.setVisible(true);
87         this.setLayout(null);
88         // 初始化用于拖动的bar
89         bar = new JBPanel();
90         // 滚动条颜色
91         bar.setBackground(ColorConstants.SCROLLBAR);
92         // 第一次出现时表明Chart刚刚铺满界面,此时滚动条应和整个Chart一样长,后面随着时间推移,滚动条慢慢缩小
93         moveScopeWidth = parent.getWidth();
94         bar.setPreferredSize(new Dimension(moveScopeWidth, BAR_HEIGHT));
95         bar.setBounds(0, 0, bar.getPreferredSize().width, bar.getPreferredSize().height);
96         this.add(bar);
97         this.setPreferredSize(new Dimension(moveScopeWidth, BAR_HEIGHT));
98     }
99 
100     /**
101      * 添加监听器
102      */
addListener()103     private void addListener() {
104         this.addMouseMotionListener(new MouseAdapter() {
105             @Override
106             public void mouseDragged(MouseEvent mouseEvent) {
107                 dragEvent(mouseEvent);
108             }
109         });
110         this.addMouseListener(new MouseAdapter() {
111             @Override
112             public void mousePressed(MouseEvent mouseEvent) {
113                 pressEvent(mouseEvent);
114             }
115 
116             @Override
117             public void mouseReleased(MouseEvent mouseEvent) {
118                 releaseEvent();
119             }
120         });
121     }
122 
123     /**
124      * 自定义鼠标按下事件
125      *
126      * @param mouseEvent MouseEvent
127      */
pressEvent(MouseEvent mouseEvent)128     private void pressEvent(MouseEvent mouseEvent) {
129         // 按下时计算滚动条起点坐标和鼠标点之间的偏移量
130         offsetPixel = mouseEvent.getX() - bar.getX();
131     }
132 
133     /**
134      * 自定义鼠标释放事件
135      */
releaseEvent()136     private void releaseEvent() {
137         // 恢复偏移量为初始值
138         offsetPixel = INITIAL_VALUE;
139     }
140 
141     /**
142      * 自定义拖拽事件
143      *
144      * @param mouseEvent MouseEvent
145      */
dragEvent(MouseEvent mouseEvent)146     private void dragEvent(MouseEvent mouseEvent) {
147         int newLocationX = getNewLocationX(mouseEvent);
148         int barWidth = bar.getWidth();
149         if (moveScopeWidth == (barWidth + newLocationX) && !parent.isStop()) {
150             setRestartRefresh();
151             return;
152         }
153         // 拖拽时停止刷新Chart
154         if (!parent.isStop()) {
155             setPauseRefresh();
156         }
157         // 计算拖动后的应显示的时间区域
158         calcTimeRange(newLocationX);
159         // 更新滚动条位置
160         bar.setBounds(newLocationX, 0, bar.getWidth(), BAR_HEIGHT);
161     }
162 
163     /**
164      * 设置重启状态
165      */
setRestartRefresh()166     private void setRestartRefresh() {
167         parent.getPublisher().restartRefresh();
168         parent.setPause(false);
169     }
170 
171     /**
172      * 设置暂停状态
173      */
setPauseRefresh()174     private void setPauseRefresh() {
175         parent.getPublisher().pauseRefresh();
176         parent.setPause(true);
177     }
178 
179     /**
180      * 通过拖动距离计算时间范围的变化
181      *
182      * @param newLocationX 滚动条新位置的X坐标
183      */
calcTimeRange(int newLocationX)184     private void calcTimeRange(int newLocationX) {
185         // 空白宽度:组件宽度 - 滚动条宽度
186         int emptyWidth = moveScopeWidth - bar.getWidth();
187         if (emptyWidth == 0) {
188             return;
189         }
190         // 隐藏掉的时间:lastTime - displayTime
191         ChartStandard standard = parent.getPublisher().getStandard();
192         int lastTime = (int) (standard.getLastTimestamp() - standard.getFirstTimestamp());
193         int hideTime = lastTime - standard.getMaxDisplayMillis();
194         // 计算未占用的地方,1px代表多少隐藏时间,得到一个比例
195         BigDecimal scale = divide(hideTime, emptyWidth);
196         // 拿到滚动条移动了多少px:新位置减去bar当前位置
197         int movePixel = newLocationX - bar.getX();
198         // 计算出时间应该偏移多少
199         int timeOffset = multiply(scale, movePixel);
200         ChartDataRange oldRange = standard.getDisplayRange();
201         int newStart = oldRange.getStartTime() + timeOffset;
202         // 如果newStart不能小于0
203         if (newStart < 0) {
204             newStart = 0;
205         }
206         // 如果拖至最左边和最右边,需要特殊处理
207         if (newLocationX == 0) {
208             newStart = 0;
209         }
210         if (newLocationX == moveScopeWidth - bar.getWidth()) {
211             newStart = lastTime - standard.getMaxDisplayMillis();
212         }
213         int newEnd = newStart + standard.getMaxDisplayMillis();
214         parent.getPublisher().notifyRefresh(newStart, newEnd);
215     }
216 
217     /**
218      * 鼠标移动后,获取新的滚动条X坐标
219      *
220      * @param mouseEvent MouseEvent
221      * @return int
222      */
getNewLocationX(MouseEvent mouseEvent)223     private int getNewLocationX(MouseEvent mouseEvent) {
224         // 拖拽后滚动条的起点X坐标即为鼠标位置-偏移量
225         int newLocationX = mouseEvent.getX() - offsetPixel;
226         // 如果起始坐标从左侧超出了父Panel,需要限制一下newLocationX
227         newLocationX = Math.max(newLocationX, 0);
228         // 同理,结束坐标超出了父Panel的宽度,也要限制newLocationX,不能超出
229         int newLocationEndX = newLocationX + bar.getWidth();
230         if (newLocationEndX >= parent.getWidth()) {
231             newLocationX = parent.getWidth() - bar.getWidth();
232         }
233         return newLocationX;
234     }
235 
236     /**
237      * 计算并调整滚动条调整后的宽度和位置
238      */
resizeAndReposition()239     public void resizeAndReposition() {
240         ChartStandard standard = parent.getPublisher().getStandard();
241         int lastTime = (int) (standard.getLastTimestamp() - standard.getFirstTimestamp());
242         // 最后的时间小于最大展示时间,则不需要滚动条
243         int maxDisplay = parent.getPublisher().getStandard().getMaxDisplayMillis();
244         if (lastTime <= maxDisplay) {
245             return;
246         }
247         // 每次调整位置和长度时,需要更新moveScopeWidth,因为界面可能缩放
248         moveScopeWidth = parent.getWidth();
249         // 用户可能把滚动条拖动值中间,然后触发当前方法,
250         // 所以要计算进度条前面的空白长度来确定滚动条位置
251         // 计算进度条前面的空白长度
252         int startTime = standard.getDisplayRange().getStartTime();
253         BigDecimal leftEmptyRatio = divide(startTime, lastTime);
254         int leftEmptyWidth = multiply(leftEmptyRatio, moveScopeWidth);
255         // 计算进度条的比例:当前展示时间/本次任务最后的时间
256         BigDecimal barRatio = divide(maxDisplay, lastTime);
257         int barNewWidth = multiply(barRatio, moveScopeWidth);
258         // 保证宽度不能超过最小值,否则会滚动条会缩小到看不清
259         if (barNewWidth < MIN_WIDTH) {
260             barNewWidth = MIN_WIDTH;
261             // 保证宽度不能超过最小值后,滚动条的比例和空白的比例已经不一样了,需要重新计算,
262             // 比例和宽度均减去maxDisplay(barNewWidth)
263             leftEmptyRatio = divide(startTime, lastTime - maxDisplay);
264             leftEmptyWidth = multiply(leftEmptyRatio, moveScopeWidth - barNewWidth);
265         }
266         bar.setPreferredSize(new Dimension(barNewWidth, BAR_HEIGHT));
267         // Chart再刷新时,滚动条一定在最右边,由于上面的运算会丢失精度,
268         // 会导致滚动条在闪烁,这里做个运算保证滚动条一直在右侧
269         if (parent.getPublisher().isRefreshing()) {
270             leftEmptyWidth = moveScopeWidth - barNewWidth;
271         }
272         // 更新进度条位置
273         bar.setBounds(leftEmptyWidth, 0, barNewWidth, BAR_HEIGHT);
274         this.repaint();
275         this.revalidate();
276     }
277 }
278