"""
@Name: Empty Plugin Parent Template
@Description: This is the Empty Plugin Class that will be parent class to empty plugins that can be
shipped with the application. These empty plugins lead you to the download page.
@Created on: Jan-2022
@Created by: Vinimay Kaul
@Last Modified: 21-Jan-2022
@Last Modified by: Vinimay Kaul
"""
from kivy.uix.gridlayout import GridLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.modalview import ModalView
from kivy.uix.image import Image
from kivy.graphics import Color, Rectangle
from kivy.clock import Clock
from kivy.animation import Animation
from kivy.lang.builder import Builder
import webbrowser
from kivy.core.window import Window
from csengine.utility import DebugUtility, PluginUtility, OSUtility, NotificationUtility
from csengine.globals import ActivePlugin
import csengine.globals as csglobal
from csengine.uix import PopUpYESNO, ItemInformationBox, ShowWaiting, RoundedButton
from csengine.utility import PluginUtility
from . import globals
from pathlib import Path
import random, math, threading
from functools import partial
from threading import Event
[docs]
class EmptyPluginBase:
#############################################################################################
# CORE FUNCTIONALITY METHODS. DO NOT MODIFY OR OVERRIDE
# If you want to know which functions you can override go to the section at the bottom
# of the code. modifyController(), play(), stop(), preload(), getFrame()
#############################################################################################
"""
Represents am Empty Plugin Base Template with its properties and methods.
:ivar normalBG: The path to the *Background Image* to be shown as a thumbnail in CamSkool for the plugin. (Default: "")
:vartype normalBG: str
:ivar downBG: The path to the *MouseOver Image* to be shown when the user mouseovers the thumbnail. (Default: "")
:vartype downBG: str
:ivar controllerBG: The path to the *Background Image* to be shown in the plugin's controller. (Default: "")
:vartype controllerBG: str
:ivar name: The name of the Plygin as shown in the application. (Default: "Sample Plugin")
:vartype name: str
:ivar description: The short description of the plugin that will be used in the app. (Default: "No Information available.")
:vartype description: str
:ivar tags_raw: The tags for the plugin that will be used in search. (Default: "plugin,camskool,untagged")
:vartype tags_raw: str
:ivar plugin_path: The path where the plugin will be installed or found. This path should always be CEEnvironment.GetFullPluginsPath() / plugin_name, unless you specifically want to install the plugin at a different location.
:vartype plugin_path: str
:ivar type: This value defines where the plugin will show up in the app. The type of plugin decides wehre it shows.
The values have to be "ANIM", "IMAGE" or "TOOL". The names can be confusing because the design process and ideas
about plugin types and categories changed over time but the values are still the same as the first categorisations.
Changing this in the code is easy, but it will mean that all the plugins have to be recompiled and resigned and re-uploaded. (Default: "TOOL")
:vartype type: str
:ivar platform: This value was used to make sure that the platform specific plugins could be made.
Check globals class for options like globals.COMPATIBILITY_ALL, globals.COMPATIBILITY_WIN_ONLY, globals.COMPATIBILITY_MAC_ONLY
:vartype platform: int
:ivar stackable: This value allows the application to know if this plugin can be stacked over other plugins or not.
Some plugins cannot be stacked over other plugins because of their functionality, but most plugins can be. (Default: False)
:vartype stackable: bool
:ivar infoImagePath:
:vartype infoImagePath:
:ivar iconImage:
:vartype iconImage:
:ivar downloadLink:
:vartype downloadLink:
:ivar pipes: The pipes array used to communicate between modules.
:vartype pipes: pipes Array
:ivar writerEvent: A Thread Event used to write the Audio data
:vartype writerEvent: threads.Event
:ivar audioReRoute: Value defines if the Audio will be rerouted to the speakers or not
:vartype audioReRoute: bool
:ivar requiresRGB: This value determines if the frame needs to return the RGB value or RGBA. If true then frames will be returned as RGBA else RGB
:vartype requiredRG: bool
"""
pipes = []
writerEvent = None
audioOverlayChannel = None
audioReRoute = False
requiresRGB = False
# The constructor __init__
def __init__(self, **kwargs):
self.normalBG = kwargs.get("normalBG", "")
self.downBG = kwargs.get("downBG", "")
self.controllerBG = kwargs.get("controllerBG", "")
self.name = kwargs.get("name", "Sample Plugin")
self.description = kwargs.get("description", "No Information available.")
self.tags_raw = kwargs.get("tags", "plugin,camskool,untagged")
self.plugin_path = kwargs.get("plugin_path", "")
self.plugin_name = kwargs.get("plugin_name", None)
self.type = kwargs.get("type", "TOOL")
self.platform = kwargs.get("platform", 0)
self.stackable = kwargs.get("stackable", False)
self.infoImagePath = kwargs.get("info_image", "./CommonMedia/sampleInfo.png")
self.iconImage = kwargs.get("icon", "./CommonMedia/sampleIcon.png")
self.downloadLink = kwargs.get("link", "https://camskool.com/store")
if self.plugin_path is None:
return
self.originalInstance = self # will be used to connect multiple instances to original one.
self.tags = self.tags_raw.split(",")
self.controllerWidth = "360sp"
self.controllerHeight = '360sp'
self.iconSize = ("92sp", "92sp")
self.appWidth = 360
self.appHeight = 650
self.AddPluginToList()
self.controllerCanvas = None
self.controller = None
self.confirmOnClose = True
self.confirmCloseText = "Are you sure you want to remove [b]" + self.name + "[/b] " \
"from the stack? You will lose your settings."
self.addFlag = False
self.mainGridLayout = None
self.openButton = None
self.infoButton = None
self.favoriteButton = ToggleButton()
self.favoriteButtonCon = ToggleButton()
self.loader = None
self.preloadThread = None
self.preloadClock = None
self.preloadProgress = 0 # This value can be modified to change the progressbar status
self.loaderText = self.name + " is loading. Please wait."
self.ObservePlatformCompatibility()
PluginUtility.LoadFavoritesFromFile(self)
self.mainGL = None
self.controllerGL = None
# Threading Event to block writing function
self.writerEvent = Event()
self.writerEvent.set()
self.audioReRoute = False
## The Plugins UI on the List. This will remain as is. DO NO OVERRIDE
[docs]
def createPluginUI(self):
fav_state = "normal"
if self in PluginUtility.Plg_Favorites:
fav_state = "down"
mainGL = GridLayout(cols=1, size_hint=(None, None), width="170sp", height="130sp")
gL1 = GridLayout(cols=1, size_hint=(None, None), width="170sp", height="100sp")
gL2 = GridLayout(cols=5)
gL3 = GridLayout(cols=1, size_hint=(None, None), width="35sp", height="35sp")
gL4 = GridLayout(cols=1)
gL5 = GridLayout(cols=1, size_hint=(None, None), width="35sp", height="35sp")
gL6 = GridLayout(cols=3)
gL7 = GridLayout(cols=3, size_hint_y=None, height="50sp")
gL8 = GridLayout(cols=3, size_hint_y=None, height="60sp")
gL9 = GridLayout(cols=1)
self.mainGridLayout = gL1
tgl_fav = ToggleButton(text="", background_normal="./CommonMedia/fav_button.png",
background_down="./CommonMedia/fav_button_D.png", state=fav_state, disabled=True)
#tgl_fav.bind(on_press=self.addRemoveFavorites)
btn_info = Button(text="", background_normal="./CommonMedia/info_button.png",
background_down="./CommonMedia/info_button_D.png", disabled=True)
btn_info.bind(on_release=self.showPluginInfo)
btn_open = Button(size_hint=(None, None), width="85sp", height="28sp", text="Download", color=(0, 0, 0, 1),
background_normal="./CommonMedia/open_button.png",
background_down="./CommonMedia/open_button_D.png")
btn_open.bind(on_release=self.downloadPlugin)
self.openButton = btn_open
self.openButton.opacity = 0
self.infoButton = btn_info
self.favoriteButton = tgl_fav
gL1.add_widget(Label(size_hint_y=None, height="10sp"))
gL1.add_widget(gL2)
gL2.add_widget(Label(size_hint=(None, None), width="15sp", height="15sp"))
gL2.add_widget(gL3)
# gL3.add_widget(btn_info)
gL3.add_widget(Label())
gL2.add_widget(gL4)
gL4.add_widget(Label())
gL2.add_widget(gL5)
# gL5.add_widget(tgl_fav)
gL5.add_widget(Label())
gL2.add_widget(Label(text="", size_hint=(None, None), width="15sp", height="15sp"))
gL1.add_widget(gL6)
gL6.add_widget(Label())
gL6.add_widget(gL7)
gL7.add_widget(Label())
gL7.add_widget(Label())
gL7.add_widget(Label())
gL6.add_widget(Label())
gL1.add_widget(gL8)
gL8.add_widget(Label())
gL8.add_widget(btn_open)
gL8.add_widget(Label())
gL1.add_widget(gL9)
gL9.add_widget(Label(text="", size_hint_y=None, height="5sp"))
mainGL.add_widget(gL1)
mainGL.add_widget(Label(text=self.name, size_hint_y=None, height="13sp", color=(.5, .5, .5, 1)))
gL1.bind(pos=self.onMouseOver) # Required as the GridLayout position values don't change.
# UPDATE MOVED TO MOUSE MOVE
Window.bind(mouse_pos=self.onMouseOver)
return mainGL
# Callback Function that updates the new positions of items inside Main GridLayout
# UPDATE > Not required anymore as the event is now triggered from Mouse Move
[docs]
def callback_posChange(self, instance, value):
with instance.canvas.before:
Color(1, 1, 1, 1)
Rectangle(source=str(self.normalBG), pos=(instance.x, instance.y), size=(instance.width, instance.height))
# Call back function for Mouse Over.
[docs]
def onMouseOver(self, *args):
if self.mainGridLayout is not None:
x = args[1][0]
y = args[1][1]
coord = self.mainGridLayout.to_widget(x=x, y=y,
relative=False) # IMPORTANT! TO MAP WINDOW X, Y COORD
# TO WIDGET COORDINATES
if self.mainGridLayout.collide_point(*coord):
self.mainGridLayout.canvas.before.clear() # Clear Canvas.Before
with self.mainGridLayout.canvas.before: # Add Updated Image
Color(1, 1, 1, 1)
Rectangle(source=str(self.downBG), pos=(self.mainGridLayout.x, self.mainGridLayout.y),
size=(self.mainGridLayout.width, self.mainGridLayout.height))
self.infoButton.opacity = 1
self.openButton.opacity = 1
self.favoriteButton.opacity = 1
else:
self.mainGridLayout.canvas.before.clear() # Clear Canvas.Before
with self.mainGridLayout.canvas.before: # Add Updated Image
Color(1, 1, 1, 1)
Rectangle(source=str(self.normalBG), pos=(self.mainGridLayout.x, self.mainGridLayout.y),
size=(self.mainGridLayout.width, self.mainGridLayout.height))
self.infoButton.opacity = 0
self.openButton.opacity = 0
if not self.favoriteButton.state == "down":
self.favoriteButton.opacity = 0
# This function creates the UI for the plugin in the search window
[docs]
def createSearchListUI(self):
gL = GridLayout(cols=3, padding=["10sp", "0sp", "10sp", "0sp"], size_hint_y=None, height="50sp")
gLlbl = GridLayout(cols=1, size_hint_y="50sp")
gLBtn = GridLayout(cols=1, padding=["0sp", "10sp", "0sp", "10sp"], size_hint_y=None, height="50sp")
img = Image(source=str(self.iconImage), size_hint=(None, None), width="50sp", height="50sp")
#btnInfo = RoundedButton(text="Info", on_release=self.showPluginInfo, background_color=(1, .506, 0.07, 1), disabled=True)
#btnInfo.makeQuarterButton()
btnOpen = RoundedButton(text="Download", on_release=self.downloadPlugin, background_color=(0, 0.447, 0.737, 1))
btnOpen.makeHalfButton()
lblName = Label(text="[b]" + str(self.name) + "[/b]", markup=True , size_hint_x=None, width="162sp", halign="left", valign="middle")
lblNameKV = """
Label:
text: "{item}"
text_size: self.size
markup: True
size_hint_x: None
width: dp(165)
halign: "left"
valign: "middle"
"""
# lblName = Builder.load_string(lblNameKV.format(item="[b] " + str(self.name) + "[/b]"))
#gLBtn.add_widget(btnInfo)
gLBtn.add_widget(btnOpen)
gL.add_widget(img)
gL.add_widget(lblName)
gL.add_widget(gLBtn)
gL.bind(size=self.updateLayoutPosition)
return gL
# This function is the callback function for on Position or Size change of the Layout in SearchList UI
[docs]
def updateLayoutPosition(self, *args):
with args[0].canvas.before:
Color(.96, .96, .96, 1)
Rectangle(size=args[0].size, pos=args[0].pos)
# This function creates the UI if it is in the Favorites List
[docs]
def createFavoritesUI(self):
pass
# This function when called shows the Information Box of the plugin, using csengine.uix.ItemInformationBox class
[docs]
def showPluginInfo(self, instance):
ib = ItemInformationBox(name=self.name, desc=self.description, rating=self.getItemRating(),
total_votes=self.getTotalVotes(), plugin_path=str(self.plugin_path),
item_image=str(self.infoImagePath), on_open=self.openPlugin)
ib.show()
# This function will be reading the rating of the plugin and returning that value. The rating will be shown as stars
[docs]
def getItemRating(self):
# Write Code to get the Current Item Rating from Web
return round((random.random() * 5), 1)
# This function returns the Total number of votes read for the current plugin
[docs]
def getTotalVotes(self):
# Write code to get the Current Plugins total voters
return math.floor(random.random() * 1000)
# This function checks if the current plugin is compatible with the current OS. marks the self.addFlag value
# if self.addFlag is False, the plugin will not show up in the list
# if self.addFlag is True only then it will show in the list. DO NOT OVERRIDE
# The following function adds the Plugin to the appropriate Lists
# DO NOT OVERRIDE
[docs]
def AddPluginToList(self):
self.ObservePlatformCompatibility()
if self.type == 'TOOL' and self.addFlag:
DebugUtility.Log("Adding Plugin : " + self.name + " to the Plg_Tool_lst")
PluginUtility.Plg_Tool_List.append(self)
elif self.type == 'ANIM' and self.addFlag:
DebugUtility.Log("Adding Plugin : " + self.name + " to the Plg_Anim_lst")
PluginUtility.Plg_Anim_List.append(self)
elif self.type == 'IMAGE' and self.addFlag:
DebugUtility.Log("Adding Plugin : " + self.name + " to the Plg_Image_lst")
PluginUtility.Plg_Img_List.append(self)
elif self.type == 'SOUND' and self.addFlag:
DebugUtility.Log("Adding Plugin : " + self.name + " to the Plg_Sound_lst")
PluginUtility.Plg_Sound_List.append(self)
else:
if self.addFlag:
DebugUtility.Log("Adding Plugin : " + self.name + " to the Plg_Tool_lst")
PluginUtility.Plg_Tool_List.append(self)
# If Plugin can be stacked
if self.stackable:
DebugUtility.Log("Adding Plugin : " + self.name + " to the Plg_SEffects_lst")
PluginUtility.Plg_SEffect_List.append(self)
## This method is responsible for opening the Web Browser and goto the image link
[docs]
def downloadPlugin(self, *args):
DebugUtility.Log("Downloading " + str(self.name) + " from " + str(self.downloadLink))
webbrowser.open(self.downloadLink)
# END OF CLASS