class ACLIBCollapsable(ACLabel): TEXTURES = { 'plus': path(TEXTURE_DIR, 'plus.png'), 'minus': path(TEXTURE_DIR, 'minus.png') } def __init__(self, parent: ACWidget, target: ACWidget): super().__init__(parent) self._collapsed = False self._grid = ACGrid(3, 2, self) self._button = ACButton(self._grid) self._content = ACLabel(self._grid) self._button.background_color = WHITE self._button.background_texture = ACLIBCollapsable.TEXTURES['plus'] self._grid.add(self._button, 0, 0) self._grid.add(self._content, 0, 1) self.content = target @property def content(self): return self._content.child @content.setter def content(self, content: ACWidget): self._content.child = content @property def collapsed(self) -> bool: return self._collapsed @collapsed.setter def collapsed(self, collapsed: bool): self._collapsed = collapsed if collapsed: self._button.background_texture = ACLIBCollapsable.TEXTURES[ 'minus'] self._content.visible = False else: self._button.background_texture = ACLIBCollapsable.TEXTURES['plus'] self._content.visible = True
class Tyres(ACApp): def __init__(self, data: ACData = None, meta: ACMeta = None): super().__init__('ACLIB Tyres', 200, 200, 98, 160, True, True) self.hide_decoration() self.background = False self.border = False self._data = data self._meta = meta self._grid = ACGrid(5, 9, self) # For each tyre there is a separate class to avoid redundancy of index checking. self.fl = Tyre(Tyre.FL, self._grid, self._data, self._meta) self.fr = Tyre(Tyre.FR, self._grid, self._data, self._meta) self.rl = Tyre(Tyre.RL, self._grid, self._data, self._meta) self.rr = Tyre(Tyre.RR, self._grid, self._data, self._meta) self._grid.add(self.fl, 0, 0, 2, 4) self._grid.add(self.fr, 3, 0, 2, 4) self._grid.add(self.rl, 0, 5, 2, 4) self._grid.add(self.rr, 3, 5, 2, 4)
class Fuel(ACApp): def __init__(self, data: ACData = None, meta: ACMeta = None): super().__init__('ACLIB Fuel', 200, 200, 300, 100, True, True) self.hide_decoration() self._data = data self._meta = meta # Init self._normal_font = Font('Roboto Mono') self._normal_font.bold = 1 self._normal_font.color = WHITE self._main_grid = ACGrid(5, 4, self) self._grid = ACGrid(3, 3, self._main_grid) self._avg_km = ACLabel(self._grid, 'Ø 00.0 l/km', 'center', self._normal_font) self._avg_lap = ACLabel(self._grid, 'Ø 00.0 l/L', 'center', self._normal_font) self._avg_min = ACLabel(self._grid, 'Ø 00.0 l/min', 'center', self._normal_font) self._fuel = ACLabel(self._grid, '+ 00.0 l', 'center', self._normal_font) self._km = ACLabel(self._grid, '+ 00.0 km', 'center', self._normal_font) self._lap = ACLabel(self._grid, '+ 00.0 L', 'center', self._normal_font) self._min = ACLabel(self._grid, '+ 00.0 min', 'center', self._normal_font) # Style self.background_color = Color(0, 0, 0, 0.5) #self._fuel.background_color = Color(0.75, 0.75, 0, 0.75) self._main_grid.add(self._fuel, 3, 0, 2, 1) self._main_grid.add(self._avg_km, 1, 1, 2, 1) self._main_grid.add(self._km, 3, 1, 2, 1) self._main_grid.add(self._avg_lap, 1, 2, 2, 1) self._main_grid.add(self._lap, 3, 2, 2, 1) self._main_grid.add(self._avg_min, 1, 3, 2, 1) self._main_grid.add(self._min, 3, 3, 2, 1) # Data self._monitor = False self._timer = 0 self._track_len = 0 self._km_fuel_ref = 0 self._lap_fuel_ref = 0 self._min_fuel_ref = 0 self._km_fuel_avg = 0 self._lap_fuel_avg = 0 self._min_fuel_avg = 0 self._data.on(ACData.EVENT.READY, self._on_ready) def _timer(self): pass def _reset(self): self._km_fuel_ref = self.fuel self._lap_fuel_ref = self.fuel self._min_fuel_ref = self.fuel def _on_ready(self): self._reset() self._track_len = self._data.environment.track_length / 1000 self._avg_km.text = 'Ø 00.0 l/km' self._avg_lap.text = 'Ø 00.0 l/L' self._avg_min.text = 'Ø 00.0 l/min' self._fuel.text = '{:2.1f} l'.format(self.fuel) self._km.text = '+ 00.0 km' self._lap.text = '+ 00.0 L' self._min.text = '+ 00.0 min' self._data.on(ACData.EVENT.SESSION_CHANGED, self.on_session) self._data.on(ACData.EVENT.SESSION_STATUS_CHANGED, self.on_session_status) self._data.on(ACData.EVENT.PIT_ENTERED, self.on_pit_entered) self._data.on(ACData.EVENT.PIT_LEFT, self.on_pit_left) self._data.on(ACData.EVENT.KM_CHANGED, self.on_km) self._data.on(ACData.EVENT.LAP_CHANGED, self.on_lap) self._data.on(ACData.EVENT.FUEL_CHANGED, self.on_fuel) def on_session(self, session: int): self._monitor = False def on_session_status(self, status: int): self._monitor = False def on_pit_entered(self): self._monitor = False def on_pit_left(self): self._reset() self._monitor = True def on_fuel(self, fuel: float): self._fuel.text = '{} l'.format(self.fuel) def on_km(self, km: int): if not self._monitor: return self._km_fuel_avg = self.avg(self._km_fuel_avg, self._km_fuel_ref) self._km_fuel_ref = self.fuel enough_for = self.enough_for(self._km_fuel_avg) self._avg_km.text = 'Ø {:2.1f} l/km'.format(self._km_fuel_avg) self._km.text = '+ {:2.1f} km'.format( self.enough_for(self._km_fuel_avg)) #if self._data.session.session == Type.RACE: if enough_for < self.race_dist_left(True): self._km.background_color = Color(1, 0, 0, 1) else: self._km.background_color = Color(0, 0, 0, 0) def on_lap(self, lap: int): if not self._monitor: self._monitor = True return self._lap_fuel_avg = self.avg(self._lap_fuel_avg, self._lap_fuel_ref) self._lap_fuel_ref = self.fuel enough_for = self.enough_for(self._lap_fuel_avg) self._avg_lap.text = 'Ø {:2.1f} l/L'.format(self._lap_fuel_avg) self._lap.text = '+ {:2.1f} L'.format(enough_for) #if self._data.session.session == Type.RACE: if enough_for < self.race_dist_left(): self._lap.background_color = Color(1, 0, 0, 1) else: self._lap.background_color = Color(0, 0, 0, 0) def on_min(self, km: int): if not self._monitor: return self._min_fuel_avg = self.avg(self._min_fuel_avg, self._min_fuel_ref) self._min_fuel_ref = self.fuel enough_for = self.enough_for(self._min_fuel_avg) self._avg_min.text = 'Ø {:2.1f} l/min'.format(self._min_fuel_avg) self._min.text = '+ {:2.1f} min'.format(enough_for) # if self._data.session.session == Type.RACE: if enough_for < self.race_dist_left(): self._min.background_color = Color(1, 0, 0, 1) else: self._min.background_color = Color(0, 0, 0, 0) @property def fuel(self): return round(self._data.car.fuel, 1) def race_dist(self, km: bool = False): if self._data.session.is_timed_race: return self._data.session.time_left else: if km: return self._data.session.laps * self._track_len else: return self._data.session.laps def race_dist_left(self, km: bool = False): if self._data.session.is_timed_race: return self._data.session.time_left else: if km: return (self._data.session.laps - self._data.timing.lap) * self._track_len else: return self._data.session.laps - self._data.timing.lap def enough_for(self, avg: float): if avg == 0: return 0 return round(self._data.car.fuel / avg, 1) def avg(self, last: float, ref: float): if last == 0: return ref - self.fuel return round((last + (ref - self.fuel)) / 2, 1)
class Comparator(ACApp): TEXTURES = { 'left': path(TEXTURE_DIR, 'arrow-left-slim.png'), 'right': path(TEXTURE_DIR, 'arrow-right-slim.png') } MODE = ['Lap Time', 'Sector Time'] def __init__(self, data: ACData = None, meta: ACMeta = None): super().__init__('ACLIB Comparator', 200, 200, 300, 55, True) self.hide_decoration() self.background_color = Color(0.1, 0.1, 0.1, 0.5) self._data = data self._meta = meta self._mode = 0 self._opponents = [] self._grid = ACGrid(12, 11, self) self._header = CR_Header(self._grid, 'Lap Time') self._left = CR_Icon(self._grid, Comparator.TEXTURES['left']) self._right = CR_Icon(self._grid, Comparator.TEXTURES['right']) self._add = CR_Button(self._grid, 'Add Opponent') self._remove = CR_Button(self._grid, 'Remove Opponent') self._grid.add(self._left, 0, 0, 1, 6) self._grid.add(self._header, 1, 0, 10, 6) self._grid.add(self._right, 11, 0, 1, 6) self._grid.add(self._add, 0, 6, 6, 5) self._grid.add(self._remove, 6, 6, 6, 5) self._left.on(ACWidget.EVENT.ON_MOUSE_DOWN, self._on_left) self._right.on(ACWidget.EVENT.ON_MOUSE_DOWN, self._on_right) #self._add.on(ACWidget.EVENT.ON_MOUSE_DOWN, self._on_add) #self._remove.on(ACWidget.EVENT.ON_MOUSE_DOWN, self._on_remove) self._add_opponent(0) self._add_opponent(1) def _add_opponent(self, i): w, h = self.size opponent = ComparatorRow(self._grid, self._data, self._meta) opponent.position = 0, h + i * 50 opponent.size = w, 50 self._opponents.append(opponent) # self._grid.add_rows(2) # w, h = self.size # self.size = w, h + 40 # self._grid.add(opponent, 0, 4 + len(self._opponents) * 2, 12, 2) def _on_mode_changed(self): for opponent in self._opponents: opponent.change_mode(self._mode) def _on_left(self, *args): if self._mode > 0: self._mode -= 1 else: self._mode = len(Comparator.MODE) - 1 self._header.text = Comparator.MODE[self._mode] self._on_mode_changed() def _on_right(self, *args): if self._mode < len(Comparator.MODE) - 1: self._mode += 1 else: self._mode = 0 self._header.text = Comparator.MODE[self._mode] self._on_mode_changed() def _on_add(self, *args): self._grid.add_rows(2) w, h = self.size self.size = w, h + 40 def _on_remove(self, *args): if len(self._opponents) > 0: self._grid.remove_rows(2) w, h = self.size self.size = w, h - 40 def update(self, delta: float): super().update(delta) for o in self._opponents: o.update(delta)
class Camera(ACApp): def __init__(self, data: ACData = None, meta: ACMeta = None): super().__init__('ACLIB_Camera', 200, 200, 150, 250) self.hide_decoration() self.background_color = TRANSPARENT self._data = data self._grid = ACGrid(2, 11, self) self._prev = ACLabel(self._grid, '<-', 'center') self._next = ACLabel(self._grid, '->', 'center') self._prev.background_color = DARKGRAY self._next.background_color = DARKGRAY self._grid.add(self._prev, 0, 0) self._grid.add(self._next, 1, 0) self._prev.on(ACWidget.EVENT.ON_MOUSE_DOWN, self.prev) self._next.on(ACWidget.EVENT.ON_MOUSE_DOWN, self.next) self._cameras = ['Cockpit', 'Car', 'Drivable', 'Track', 'Helicopter', 'OnBoardFree', 'Free', 'Random', 'ImageGeneratorCamera', 'Start'] self._labels = [] self._cam_id = 0 self._active_font = Font('Roboto Mono') self._active_font.size = 12 self._active_font.color = WHITE self._inactive_font = Font('Roboto Mono') self._inactive_font.size = 12 self._inactive_font.color = WHITE i = 0 for cam_id, name in enumerate(self._cameras): label = ACLabel(self._grid, name, 'center', self._inactive_font) label.background_color = BLACK label.on(ACWidget.EVENT.ON_MOUSE_DOWN, self.select) setattr(label, 'cam', cam_id) self._labels.append(label) self._grid.add(label, 0, i + 1, 2, 1) i += 1 console(label.size) self._data.on(ACData.EVENT.READY, self.init) def init(self): self._cam_id = ac.getCameraMode() self._labels[self._cam_id].font = self._active_font self._labels[self._cam_id].background_color = RED def by_id(self, cam_id): if 0 <= cam_id <= 9: self._labels[self._cam_id].font = self._inactive_font self._labels[self._cam_id].background_color = BLACK ac.setCameraMode(cam_id) self._cam_id = cam_id self._labels[self._cam_id].font = self._active_font self._labels[self._cam_id].background_color = RED def select(self, widget: ACWidget, *args): self.by_id(widget.cam) def next(self, widget: ACWidget, *args): if self._cam_id == 9: self.by_id(0) else: self.by_id(self._cam_id + 1) def prev(self, widget: ACWidget, *args): if self._cam_id == 0: self.by_id(9) else: self.by_id(self._cam_id - 1)
class UIDebug(ACApp): def __init__(self, data: ACData = None, meta: ACMeta = None): super().__init__('ACLIB UIDebug', 200, 200, 550, 80) self.hide_decoration() self.background_color = BLACK self._data = data self._active_widgets = [] self._timer = time() self._font = Font('Roboto Mono', WHITE) self._grid = ACGrid(4, 4, self) self._widget_label = ACLabel(self._grid, 'Widget:', font=self._font) self._widget_text = ACLabel(self._grid, '', font=self._font) self._position_label = ACLabel(self._grid, 'Position:', font=self._font) self._position_text = ACLabel(self._grid, '', font=self._font) self._size_label = ACLabel(self._grid, 'Size:', font=self._font) self._size_text = ACLabel(self._grid, '', font=self._font) self._property_name = ACLIBListBox(self._grid, 4) self._property_value = ACLIBListBox(self._grid, 4) col, row = 0, 0 self._grid.add(self._widget_label, col, row) self._grid.add(self._position_label, col, row + 1) self._grid.add(self._size_label, col, row + 2) col, row = 1, 0 self._grid.add(self._widget_text, col, row) self._grid.add(self._position_text, col, row + 1) self._grid.add(self._size_text, col, row + 2) col, row = 2, 0 self._grid.add(self._property_name, col, row, 1, 4) col, row = 3, 0 self._grid.add(self._property_value, col, row, 1, 4) self._data.on(ACData.EVENT.READY, self.init) self.on(ACWidget.EVENT.DISMISSED, self.dismissed) def _reset_active(self): for widget, border_color, no_render in self._active_widgets: widget.border_color = border_color widget.no_render = no_render self._active_widgets = [] self._property_name.clear() self._property_value.clear() def init(self): for _, widget in ACWidget.IDS.items(): widget.on(ACWidget.EVENT.ON_MOUSE_DOWN, self.get_info) def get_info(self, widget: ACWidget, *args): if self.active: # Children of the widget should also be highlighted. # It is expected that the next click takes at least 10ms # (otherwise it would be a pretty sick reaction time for the clicker). if time() - self._timer > 0.01: self._reset_active() self._timer = time() # Store the state of the widget. self._active_widgets.append( (widget, widget.border_color, widget.no_render)) # Highlight newly selected widgets. set_render(widget, False) widget.border_color = RED # Set Properties self._widget_text.text = widget.__class__.__name__ self._position_text.text = str(widget.position) self._size_text.text = str(widget.size) # self._property_name.add(ACLabel(self._property_name, 'Name')) # self._property_value.add(ACLabel(self._property_value, widget.__class__.__name__)) def dismissed(self, *args): self._reset_active()
class Tyre(ACLabel): COLORS = {} FL = 0 FR = 1 RL = 2 RR = 3 def __init__(self, index: int, parent: ACWidget, data: ACData, meta: ACMeta): super().__init__(parent) self.background_color = TRANSPARENT self._data = data self._meta = meta self._index = index self._wear_min, self._wear_max = 96, 100 self._grid = ACGrid(9, 12, self) # Each tyre has inner, outer, center, core and brake temperature. # Each of these locations is rendered separately by a colored texture. self._left = TyreTile(TyreTile.LEFT, self._grid, self._index, self._data, self._meta) self._right = TyreTile(TyreTile.RIGHT, self._grid, self._index, self._data, self._meta) self._center_top = TyreTile(TyreTile.CENTER, self._grid, self._index, self._data, self._meta) self._center_bot = TyreTile(TyreTile.CENTER, self._grid, self._index, self._data, self._meta) self._core = TyreTile(TyreTile.CORE, self._grid, self._index, self._data, self._meta) self._brake = TyreTile(TyreTile.BRAKE, self._grid, self._index, self._data, self._meta) self._wear = ACLIBProgressBar(self._grid, self._wear_max, self._wear_min, self._wear_max) # Left tyre if self._index % 2 == 0: self._grid.add(self._left, 0, 0, 2, 10) self._grid.add(self._center_top, 3, 0, 2, 3) self._grid.add(self._core, 3, 4, 2, 2) self._grid.add(self._center_bot, 3, 7, 2, 3) self._grid.add(self._right, 6, 0, 2, 10) self._grid.add(self._brake, 8, 2, 1, 6) # Right tyre else: self._grid.add(self._left, 1, 0, 2, 10) self._grid.add(self._center_top, 4, 0, 2, 3) self._grid.add(self._core, 4, 4, 2, 2) self._grid.add(self._center_bot, 4, 7, 2, 3) self._grid.add(self._right, 7, 0, 2, 10) self._grid.add(self._brake, 0, 2, 1, 6) self._grid.add(self._wear, 0, 11, 9, 1) self._data.on(ACData.EVENT.TYRE_WEAR_CHANGED, self._calculate_wear) # Private def _wear_value(self): return self._data.tyres.wear[self._index] def _wear_color(self, val: float): val = round(val, 1) if val in Tyre.COLORS: return Tyre.COLORS[val] color = interpolate(val, self._wear_min, self._wear_max, RED, GREEN) Tyre.COLORS[val] = color return color def _calculate_wear(self, *args): self._wear.value = self._wear_value() self._wear.progress_color = self._wear_color(self._wear_value())
class ACLIBDockableWidget(ACApp): """ Widget that allows DockWidgets to attach them to it. """ DOCK_WIDGETS = [] def __init__(self, name: str, x: int, y: int, w: int, h: int): super().__init__(name, x, y, w, h) self._dragged = False self._dockable_color = Color(0.0, 0.25, 1.0, 0.75) self._grid = ACGrid(3, 3, self) self._top = ACLabel(self._grid) self._left = ACLabel(self._grid) self._bottom = ACLabel(self._grid) self._right = ACLabel(self._grid) self._grid.add(self._top, 0, 0, 3, 1) self._grid.add(self._left, 0, 0, 1, 3) self._grid.add(self._bottom, 0, 2, 3, 1) self._grid.add(self._right, 2, 0, 1, 3) ACLIBDockableWidget.DOCK_WIDGETS.append(self) self.on(ACWidget.EVENT.ON_MOUSE_DOWN, self._on_mouse_down) self.on(ACWidget.EVENT.ON_MOUSE_UP, self._on_mouse_up) def _on_mouse_down(self, *args): self._dragged = True def _on_mouse_up(self, *args): self._dragged = False def highlight(self, other, dist: tuple): x1, y1 = other.position x2, y2 = self.position self.reset_highlight() if dist[0] > dist[1]: if x1 <= x2: self._left.background_color = self._dockable_color elif x1 > x2: self._right.background_color = self._dockable_color elif dist[0] < dist[1]: if y1 <= y2: self._top.background_color = self._dockable_color elif y1 > y2: self._bottom.background_color = self._dockable_color else: if x1 <= x2: self._left.background_color = self._dockable_color elif x1 > x2: self._right.background_color = self._dockable_color if y1 <= y2: self._top.background_color = self._dockable_color elif y1 > y2: self._bottom.background_color = self._dockable_color def reset_highlight(self): self._top.background_color = TRANSPARENT self._left.background_color = TRANSPARENT self._bottom.background_color = TRANSPARENT self._right.background_color = TRANSPARENT