touch up the gauge code

This commit is contained in:
Ryan Holmes
2017-06-12 20:45:58 -04:00
parent 2acb3e759e
commit 6c317d56ee
4 changed files with 421 additions and 450 deletions

View File

@@ -21,7 +21,7 @@
import wx
from gui.statsView import StatsView
from gui.bitmap_loader import BitmapLoader
from gui.pygauge import PyGauge
from gui.pyfa_gauge import PyGauge
from gui.utils.numberFormatter import formatAmount
import gui.mainFrame
import gui.globalEvents as GE

View File

@@ -21,7 +21,7 @@
import wx
from gui.statsView import StatsView
from gui.bitmap_loader import BitmapLoader
from gui.pygauge import PyGauge
from gui.pyfa_gauge import PyGauge
import gui.mainFrame
from gui.chrome_tabs import EVT_NOTEBOOK_PAGE_CHANGED

419
gui/pyfa_gauge.py Normal file
View File

@@ -0,0 +1,419 @@
#===============================================================================
# PyfaGauge is a generic Gauge implementation tailored for pyfa (the Python
# Fitting Assistant). It uses the easeOutQuad equation from
# caurina.transitions.Tweener to do animations
#
# ToDo: make SetGradient(<value, colour start, colour end)
# ToDo: make a solid gradient (not to->from and not dependant on value)
# ToDo: fix 0 range (currently resets range to 0.01, but this causes problems if
# we really set range at 0.01). Perhaps make it -1 and test percentage as
# a negativeor something.
# ToDo: possibly devise a way to determine transition percents on init
# (currently hardcoded)
#
#===============================================================================
import copy
import wx
from gui.utils import color as color_utils
from gui.utils import draw, anim_effects
class PyGauge(wx.Window):
def __init__(self, parent, font, max_range=100, size=(-1, 30), *args,
**kargs):
super().__init__(parent, size=size, *args, **kargs)
self._size = size
self._border_colour = wx.BLACK
self._bar_colour = None
self._bar_gradient = None
self._border_padding = 0
self._max_range = max_range
self._value = 0
self._fraction_digits = 0
self._timer_id = wx.NewId()
self._timer = None
self._oldValue = 0
self._anim_duration = 500
self._anim_step = 0
self._period = 20
self._anim_value = 0
self._anim_direction = 0
self.anim_effect = anim_effects.OUT_QUAD
# transition colors used based on how full (or overfilled) the gauge is.
self.transition_colors = [
(wx.Colour(191, 191, 191), wx.Colour(96, 191, 0)), # < 0-100%
(wx.Colour(191, 167, 96), wx.Colour(255, 191, 0)), # < 100-101%
(wx.Colour(255, 191, 0), wx.Colour(255, 128, 0)), # < 101-103%
(wx.Colour(255, 128, 0), wx.Colour(255, 0, 0)) # < 103-105%
]
self.gradient_effect = -35
self._percentage = 0
self._old_percentage = 0
self._show_remaining = False
self.font = font
self.SetBackgroundColour(wx.Colour(51, 51, 51))
self._tooltip = wx.ToolTip("0.00/100.00")
self.SetToolTip(self._tooltip)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
self.Bind(wx.EVT_TIMER, self.OnTimer)
self.Bind(wx.EVT_ENTER_WINDOW, self.OnWindowEnter)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave)
def OnWindowEnter(self, event):
self._show_remaining = True
self.Refresh()
def OnWindowLeave(self, event):
self._show_remaining = False
self.Refresh()
def GetBorderColour(self):
return self._border_colour
def SetBorderColour(self, colour: wx.Colour):
self._border_colour = colour
def GetBarColour(self):
return self._bar_colour
def SetBarColour(self, colour: wx.Colour=None):
self._bar_colour = colour
def SetFractionDigits(self, digits):
self._fraction_digits = digits
def GetBarGradient(self):
raise NotImplemented()
def SetBarGradient(self, gradient=None):
raise NotImplemented()
def GetBorderPadding(self) -> int:
return self._border_padding
def SetBorderPadding(self, padding):
self._border_padding = padding
def GetRange(self):
""" Returns the maximum value of the gauge. """
return self._max_range
def Animate(self):
if not self._timer:
self._timer = wx.Timer(self, self._timer_id)
self._anim_step = 0
self._timer.Start(self._period)
def SetRange(self, range, reinit=False, animate=True):
"""
Sets the range of the gauge. The gauge length is its
value as a proportion of the range.
"""
if self._max_range == range:
return
# we cannot have a range of zero (laws of physics, etc), so we set it
if range <= 0:
self._max_range = 0.01
else:
self._max_range = range
if reinit is False:
self._old_percentage = self._percentage
self._percentage = (self._value/self._max_range) * 100
else:
self._old_percentage = self._percentage
self._percentage = 0
self._value = 0
if animate:
self.Animate()
self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._max_range if self._max_range >0.01 else 0))
def GetValue(self):
return self._value
def SetValue(self, value, animate=True):
""" Sets the current position of the gauge. """
if self._value == value:
return
self._old_percentage = self._percentage
self._value = value
if value < 0:
self._value = 0
self._percentage = (self._value/self._max_range) * 100
if animate:
self.Animate()
self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._max_range))
def SetValueRange(self, value, range, reinit=False):
""" Set both value and range of the gauge. """
self.SetRange(range, reinit, animate=False)
self.SetValue(value, animate=False)
self.Animate()
self._tooltip.SetTip("%.2f/%.2f" %
(self._value,
self._max_range if self._max_range > 0.01 else 0))
def OnEraseBackground(self, event):
pass
def OnPaint(self, event):
dc = wx.BufferedPaintDC(self)
rect = self.GetClientRect()
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
colour = self.GetBackgroundColour()
dc.SetBrush(wx.Brush(colour))
dc.SetPen(wx.Pen(colour))
dc.DrawRectangle(rect)
value = self._percentage
if self._timer:
if self._timer.IsRunning():
value = self._anim_value
if self._border_colour:
dc.SetPen(wx.Pen(self.GetBorderColour()))
dc.DrawRectangle(rect)
pad = 1 + self.GetBorderPadding()
rect.Deflate(pad, pad)
if self.GetBarColour():
# if we have a bar color set, then we will use this
colour = self.GetBarColour()
dc.SetBrush(wx.Brush(colour))
dc.SetPen(wx.Pen(colour))
# calculate width of bar and draw it
if value > 100:
w = rect.width
else:
w = rect.width * (float(value) / 100)
r = copy.copy(rect)
r.width = w
dc.DrawRectangle(r)
else:
# if bar color is not set, then we use pre-defined transitions
# for the colors based on the percentage value
# calculate width of bar
if value > 100:
w = rect.width
else:
w = rect.width * (float(value) / 100)
r = copy.copy(rect)
r.width = w
# determine transition range number and calculate xv (which is the
# progress between the two transition ranges)
pv = value
if pv <= 100:
xv = pv/100
transition = 0
elif pv <= 101:
xv = pv - 100
transition = 1
elif pv <= 103:
xv = (pv - 101)/2
transition = 2
elif pv <= 105:
xv = (pv - 103)/2
transition = 3
else:
pv = 106
xv = pv - 100
transition = -1
if transition != -1:
start_color, end_color = self.transition_colors[transition]
color = color_utils.CalculateTransition(start_color, end_color,
xv)
else:
color = wx.Colour(191, 48, 48) # dark red
color_factor = self.gradient_effect / 100
mid_factor = (self.gradient_effect / 2) / 100
if self.gradient_effect > 0:
gradient_color = color_utils.Brighten(color, color_factor)
gradient_mid = color_utils.Brighten(color, mid_factor)
else:
gradient_color = color_utils.Darken(color, color_factor * -1)
gradient_mid = color_utils.Darken(color, mid_factor * -1)
# draw bar
gradient_bitmap = draw.DrawGradientBar(
r.width,
r.height,
gradient_mid,
color,
gradient_color
)
dc.DrawBitmap(gradient_bitmap, r.left, r.top)
# font stuff begins here
dc.SetFont(self.font)
# determine shadow position
r = copy.copy(rect)
r.left += 1
r.top += 1
if self._max_range == 0.01 and self._value > 0:
format = u'\u221e' # infinity symbol
# drop shadow
dc.SetTextForeground(wx.Colour(80, 80, 80)) # dark grey
dc.DrawLabel(format, r, wx.ALIGN_CENTER)
# text
dc.SetTextForeground(wx.WHITE)
dc.DrawLabel(format, rect, wx.ALIGN_CENTER)
else:
if not self.GetBarColour() and self._show_remaining:
# we only do these for gradients with mouse over
range = self._max_range if self._max_range > 0.01 else 0
value = range - self._value
if value < 0:
label = "over"
value = -value
else:
label = "left"
format = "{0:." + str(self._fraction_digits) + "f} " + label
else:
format = "{0:." + str(self._fraction_digits) + "f}%"
# drop shadow
dc.SetTextForeground(wx.Colour(80, 80, 80))
dc.DrawLabel(format.format(value), r, wx.ALIGN_CENTER)
# text
dc.SetTextForeground(wx.WHITE)
dc.DrawLabel(format.format(value), rect, wx.ALIGN_CENTER)
def OnTimer(self, event):
old_value = self._old_percentage
value = self._percentage
start = 0
# -1 = left direction, 1 = right direction
direction = 1 if old_value < value else -1
end = direction * (value - old_value)
self._anim_direction = direction
step = self.anim_effect(self._anim_step, start, end, self._anim_duration)
self._anim_step += self._period
if self._timer_id == event.GetId():
stop_timer = False
if self._anim_step > self._anim_duration:
stop_timer = True
# add new value to the animation if we haven't reached our goal
# otherwise, stop animation
if direction == 1:
if old_value + step < value:
self._anim_value = old_value + step
else:
stop_timer = True
else:
if old_value - step > value:
self._anim_value = old_value - step
else:
stop_timer = True
if stop_timer:
self._timer.Stop()
self.Refresh()
if __name__ == "__main__":
def frange(x, y, jump):
while x < y:
yield x
x += jump
class TestPanel(wx.Panel):
def __init__(self, parent, size=(500, 500)):
wx.Panel.__init__(self, parent, size=size)
box = wx.BoxSizer(wx.VERTICAL)
# tests the colors of transition based on percentage used
# list of test values (from 99 -> 106 in increments of 0.5)
tests = [x for x in frange(99, 106.5, .5)]
font = wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL, False)
for i, value in enumerate(tests):
self.gauge = PyGauge(self, font, size=(100, 25))
self.gauge.SetValueRange(value, 100)
self.gauge.SetFractionDigits(2)
box.Add(self.gauge, 0, wx.ALL, 2)
gauge = PyGauge(self, font, size=(100, 25))
gauge.SetBackgroundColour(wx.Colour(52, 86, 98))
gauge.SetBarColour(wx.Colour(38, 133, 198))
gauge.SetValue(59)
gauge.SetFractionDigits(1)
box.Add(gauge, 0, wx.ALL, 2)
self.SetSizer(box)
self.Layout()
# see animation going backwards with last gauge
wx.CallLater(2000, self.ChangeValues)
def ChangeValues(self):
self.gauge.SetValueRange(4, 100)
class Frame(wx.Frame):
def __init__(self, title, size=(500, 800)):
wx.Frame.__init__(self, None, title=title, size=size)
self.statusbar = self.CreateStatusBar()
main_sizer = wx.BoxSizer(wx.VERTICAL)
panel = TestPanel(self, size=size)
main_sizer.Add(panel)
self.SetSizer(main_sizer)
app = wx.App(redirect=False) # Error messages go to popup window
top = Frame("Test Chrome Tabs")
top.Show()
app.MainLoop()

View File

@@ -1,448 +0,0 @@
# --------------------------------------------------------------------------------- #
# PYFAGAUGE wxPython IMPLEMENTATION
#
# Darriele, @ 08/30/2010
# Updated: 09/07/2010
# Based on AWG : pygauge code
# --------------------------------------------------------------------------------- #
"""
PyfaGauge is a generic Gauge implementation tailored for PYFA (Python Fitting Assistant)
It uses the easeOutQuad equation from caurina.transitions.Tweener to do the animation stuff
"""
# noinspection PyPackageRequirements
import wx
import copy
from gui.utils import color
import gui.utils.draw as drawUtils
import gui.utils.anim_effects as animEffects
import gui.utils.fonts as fonts
from service.fit import Fit
class PyGauge(wx.PyWindow):
"""
This class provides a visual alternative for `wx.Gauge`. It currently
only support determinant mode (see SetValue and SetRange)
"""
def __init__(self, parent, id=wx.ID_ANY, range=100, pos=wx.DefaultPosition,
size=(-1, 30), style=0):
"""
Default class constructor.
:param `parent`: parent window. Must not be ``None``;
:param `id`: window identifier. A value of -1 indicates a default value;
:param `pos`: the control position. A value of (-1, -1) indicates a default position,
chosen by either the windowing system or wxPython, depending on platform;
:param `size`: the control size. A value of (-1, -1) indicates a default size,
chosen by either the windowing system or wxPython, depending on platform.
"""
wx.PyWindow.__init__(self, parent, id, pos, size, style)
self._size = size
self._border_colour = wx.BLACK
self._barColour = self._barColourSorted = [wx.Colour(212, 228, 255)]
self._barGradient = self._barGradientSorted = None
self._border_padding = 0
self._range = range
self._value = 0
self._fractionDigits = 0
self._timerId = wx.NewId()
self._timer = None
self._oldValue = 0
self._animDuration = 500
self._animStep = 0
self._period = 20
self._animValue = 0
self._animDirection = 0
self.animEffect = animEffects.OUT_QUAD
self.transitionsColors = [(wx.Colour(191, 191, 191, 255), wx.Colour(96, 191, 0, 255)),
(wx.Colour(191, 167, 96, 255), wx.Colour(255, 191, 0, 255)),
(wx.Colour(255, 191, 0, 255), wx.Colour(255, 128, 0, 255)),
(wx.Colour(255, 128, 0, 255), wx.Colour(255, 0, 0, 255))]
self.gradientEffect = -35
self._percentage = 0
self._oldPercentage = 0
self._showRemaining = False
self.font = wx.Font(fonts.NORMAL, wx.SWISS, wx.NORMAL, wx.NORMAL, False)
self.SetBarGradient((wx.Colour(119, 119, 119), wx.Colour(153, 153, 153)))
self.SetBackgroundColour(wx.Colour(51, 51, 51))
self._tooltip = wx.ToolTip("")
self.SetToolTip(self._tooltip)
self._tooltip.SetTip("0.00/100.00")
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
self.Bind(wx.EVT_TIMER, self.OnTimer)
self.Bind(wx.EVT_ENTER_WINDOW, self.OnWindowEnter)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave)
def OnWindowEnter(self, event):
self._showRemaining = True
self.Refresh()
def OnWindowLeave(self, event):
self._showRemaining = False
self.Refresh()
def DoGetBestSize(self):
"""
Overridden base class virtual. Determines the best size of the
button based on the label and bezel size.
"""
return wx.Size(self._size[0], self._size[1])
def GetBorderColour(self):
return self._border_colour
def SetBorderColour(self, colour):
self._border_colour = colour
SetBorderColor = SetBorderColour
GetBorderColor = GetBorderColour
def GetBarColour(self):
return self._barColour[0]
def SetBarColour(self, colour):
if not isinstance(colour, list):
self._barColour = [colour]
else:
self._barColour = list(colour)
SetBarColor = SetBarColour
GetBarColor = GetBarColour
def SetFractionDigits(self, digits):
self._fractionDigits = digits
def GetBarGradient(self):
""" Returns a tuple containing the gradient start and end colours. """
if self._barGradient is None:
return None
return self._barGradient[0]
def SetBarGradient(self, gradient=None):
"""
Sets the bar gradient. This overrides the BarColour.
:param gradient: a tuple containing the gradient start and end colours.
"""
if gradient is None:
self._barGradient = None
else:
if not isinstance(gradient, list):
self._barGradient = [gradient]
else:
self._barGradient = list(gradient)
def GetBorderPadding(self):
""" Gets the border padding. """
return self._border_padding
def SetBorderPadding(self, padding):
"""
Sets the border padding.
:param padding: pixels between the border and the progress bar.
"""
self._border_padding = padding
def GetRange(self):
""" Returns the maximum value of the gauge. """
return self._range
def Animate(self):
sFit = Fit.getInstance()
if sFit.serviceFittingOptions["enableGaugeAnimation"]:
if not self._timer:
self._timer = wx.Timer(self, self._timerId)
self._animStep = 0
self._timer.Start(self._period)
else:
self._animValue = self._percentage
self.Refresh()
def SetRange(self, range, reinit=False):
"""
Sets the range of the gauge. The gauge length is its
value as a proportion of the range.
:param reinit:
:param range: The maximum value of the gauge.
"""
if self._range == range:
return
range_ = float(range)
if range_ <= 0:
self._range = 0.01
else:
self._range = range_
if reinit is False:
self._oldPercentage = self._percentage
self._percentage = (self._value / self._range) * 100
else:
self._oldPercentage = self._percentage
self._percentage = 0
self._value = 0
self.Animate()
self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._range if self._range > 0.01 else 0))
def GetValue(self):
""" Returns the current position of the gauge. """
return self._value
def SetValue(self, value):
""" Sets the current position of the gauge. """
if self._value == value:
return
value = float(value)
self._oldPercentage = self._percentage
self._value = value
if value < 0:
self._value = 0
self._percentage = (self._value / self._range) * 100
self.Animate()
self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._range))
def SetValueRange(self, value, range, reinit=False):
if self._value == value and self._range == range:
return
range_ = float(range)
if range_ <= 0:
self._range = 0.01
else:
self._range = range_
value = float(value)
self._value = value
if value < 0:
self._value = float(0)
if reinit is False:
self._oldPercentage = self._percentage
self._percentage = (self._value / self._range) * 100
else:
self._oldPercentage = self._percentage
self._percentage = 0
self.Animate()
self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._range if self._range > 0.01 else 0))
@staticmethod
def OnEraseBackground(event):
"""
Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{PyGauge}.
:param event: a `wx.EraseEvent` event to be processed.
:note: This method is intentionally empty to reduce flicker.
"""
pass
def OnPaint(self, event):
"""
Handles the ``wx.EVT_PAINT`` event for L{PyGauge}.
:param event: a `wx.PaintEvent` event to be processed.
"""
dc = wx.BufferedPaintDC(self)
rect = self.GetClientRect()
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
colour = self.GetBackgroundColour()
dc.SetBrush(wx.Brush(colour))
dc.SetPen(wx.Pen(colour))
dc.DrawRectangleRect(rect)
value = self._percentage
if self._timer:
if self._timer.IsRunning():
value = self._animValue
if self._border_colour:
dc.SetPen(wx.Pen(self.GetBorderColour()))
dc.DrawRectangleRect(rect)
pad = 1 + self.GetBorderPadding()
rect.Deflate(pad, pad)
if self.GetBarGradient():
if value > 100:
w = rect.width
else:
w = rect.width * (float(value) / 100)
r = copy.copy(rect)
r.width = w
if r.width > 0:
# If we draw it with zero width, GTK throws errors. This way,
# only draw it if the gauge will actually show something.
# We stick other calculations in this block to avoid wasting
# time on them if not needed. See GH issue #282
pv = value
if pv <= 100:
xv = pv / 100
transition = 0
elif pv <= 101:
xv = pv - 100
transition = 1
elif pv <= 103:
xv = (pv - 101) / 2
transition = 2
elif pv <= 105:
xv = (pv - 103) / 2
transition = 3
else:
pv = 106
xv = pv - 100
transition = -1
if transition != -1:
colorS, colorE = self.transitionsColors[transition]
color = color.CalculateTransitionColor(colorS, colorE, xv)
else:
color = wx.Colour(191, 48, 48)
if self.gradientEffect > 0:
gcolor = color.BrightenColor(color, float(self.gradientEffect) / 100)
gMid = color.BrightenColor(color, float(self.gradientEffect / 2) / 100)
else:
gcolor = color.DarkenColor(color, float(-self.gradientEffect) / 100)
gMid = color.DarkenColor(color, float(-self.gradientEffect / 2) / 100)
gBmp = drawUtils.DrawGradientBar(r.width, r.height, gMid, color, gcolor)
dc.DrawBitmap(gBmp, r.left, r.top)
else:
colour = self.GetBarColour()
dc.SetBrush(wx.Brush(colour))
dc.SetPen(wx.Pen(colour))
if value > 100:
w = rect.width
else:
w = rect.width * (float(value) / 100)
r = copy.copy(rect)
r.width = w
dc.DrawRectangleRect(r)
dc.SetFont(self.font)
r = copy.copy(rect)
r.left += 1
r.top += 1
if self._range == 0.01 and self._value > 0:
formatStr = '\u221e'
dc.SetTextForeground(wx.Colour(80, 80, 80))
dc.DrawLabel(formatStr, r, wx.ALIGN_CENTER)
dc.SetTextForeground(wx.Colour(255, 255, 255))
dc.DrawLabel(formatStr, rect, wx.ALIGN_CENTER)
else:
if self.GetBarGradient() and self._showRemaining:
range = self._range if self._range > 0.01 else 0
value = range - self._value
if value < 0:
label = "over"
value = -value
else:
label = "left"
formatStr = "{0:." + str(self._fractionDigits) + "f} " + label
else:
formatStr = "{0:." + str(self._fractionDigits) + "f}%"
dc.SetTextForeground(wx.Colour(80, 80, 80))
dc.DrawLabel(formatStr.format(value), r, wx.ALIGN_CENTER)
dc.SetTextForeground(wx.Colour(255, 255, 255))
dc.DrawLabel(formatStr.format(value), rect, wx.ALIGN_CENTER)
def OnTimer(self, event):
"""
Handles the ``wx.EVT_TIMER`` event for L{PyfaGauge}.
:param event: a timer event
"""
oldValue = self._oldPercentage
value = self._percentage
start = 0
direction = 1 if oldValue < value else -1
end = direction * (value - oldValue)
self._animDirection = direction
step = self.animEffect(self._animStep, start, end, self._animDuration)
self._animStep += self._period
if self._timerId == event.GetId():
stop_timer = False
if self._animStep > self._animDuration:
stop_timer = True
if direction == 1:
if (oldValue + step) < value:
self._animValue = oldValue + step
else:
stop_timer = True
else:
if (oldValue - step) > value:
self._animValue = oldValue - step
else:
stop_timer = True
if stop_timer:
self._timer.Stop()
self.Refresh()